#!/bin/bash
# ============================================================================
# NFShmXFrame 打包脚本（使用直接复制）
# ============================================================================
#
# 本脚本使用直接复制命令进行文件复制，然后打包
# 支持全量包（all）和增量包（patch）
#
# 使用方法：
#   ./ci/package_install.sh [all|patch] [版本号] [基准版本号] [--from-commit <提交记录>]
#
# 参数说明：
#   all          - 打包全量包（包含所有文件）
#   patch        - 打包增量包（只包含变更的文件）
#   版本号        - 当前版本号（默认: dev）
#   基准版本号    - 增量包的基准版本号（仅 patch 模式需要）
#   --from-commit - 从指定提交记录开始计算manifest（可选）
#
# 环境变量：
#   GAME_DIR       - 游戏项目名称（默认: LieRen）
#   BUILD_TYPE     - 构建类型（默认: DynamicRelease）
#   INSTALL_DIR    - 文件安装的目标目录
#                     Windows: 默认 ../PackageTemp（位于项目源码目录外）
#                     Linux/Mac: 默认 ~/PackageTemp（位于用户主目录）
#   OUTPUT_DIR     - 输出目录
#                     Windows: 默认 ../Package（位于项目源码目录外）
#                     Linux/Mac: 默认 ~/Package（位于用户主目录）
#
# 示例：
#   # 打包全量包
#   ./ci/package_install.sh all v1.0.0
#
#   # 打包增量包（相对于 v1.0.0）
#   ./ci/package_install.sh patch v1.0.1 v1.0.0
#
#   # 打包全量包，从指定提交记录开始计算manifest
#   ./ci/package_install.sh all v1.0.0 --from-commit abc1234
#
#   # 打包增量包，从指定提交记录开始计算manifest
#   ./ci/package_install.sh patch v1.0.1 v1.0.0 --from-commit abc1234
#
# ============================================================================

set -e
set -u

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# 日志函数（输出到 stderr，这样在命令替换中也能看到）
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1" >&2
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1" >&2
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

# 显示帮助文档
show_help() {
    cat <<EOF
${BLUE}==========================================${NC}
${BLUE}NFShmXFrame 打包脚本（使用直接复制）${NC}
${BLUE}==========================================${NC}

${GREEN}使用方法：${NC}
  ./ci/package_install.sh [选项] [all|patch] [版本号] [基准版本号] [--from-commit <提交记录>]

${GREEN}参数说明：${NC}
  ${YELLOW}all${NC}          - 打包全量包（默认只记录 git 变更的文件）
  ${YELLOW}patch${NC}        - 打包增量包（只包含变更的文件）
  ${YELLOW}版本号${NC}        - 当前版本号（默认: dev）
  ${YELLOW}基准版本号${NC}    - 增量包的基准版本号（仅 patch 模式需要）
  ${YELLOW}--from-commit${NC} - 从指定提交记录开始计算manifest（可选）
  ${YELLOW}--full${NC}        - 完整扫描模式：扫描所有文件（默认只扫描 git 变更）

${GREEN}选项：${NC}
  ${YELLOW}-h, --help${NC}    - 显示此帮助信息

${GREEN}环境变量：${NC}
  ${YELLOW}GAME_DIR${NC}       - 游戏项目名称（默认: LieRen）
  ${YELLOW}BUILD_TYPE${NC}     - 构建类型（默认: DynamicDebug）
  ${YELLOW}INSTALL_DIR${NC}    - 文件安装的目标目录
                                  Windows: 默认 ../PackageTemp（位于项目源码目录外）
                                  Linux/Mac: 默认 ~/PackageTemp（位于用户主目录）
  ${YELLOW}OUTPUT_DIR${NC}     - 输出目录
                                  Windows: 默认 ../Package（位于项目源码目录外）
                                  Linux/Mac: 默认 ~/Package（位于用户主目录）

${GREEN}示例：${NC}
  ${BLUE}# 打包全量包${NC}
  ./ci/package_install.sh all v1.0.0

  ${BLUE}# 打包增量包（相对于 v1.0.0）${NC}
  ./ci/package_install.sh patch v1.0.1 v1.0.0

  ${BLUE}# 打包全量包，从指定提交记录开始计算manifest${NC}
  ./ci/package_install.sh all v1.0.0 --from-commit abc1234

  ${BLUE}# 打包增量包，从指定提交记录开始计算manifest${NC}
  ./ci/package_install.sh patch v1.0.1 v1.0.0 --from-commit abc1234

  ${BLUE}# 完整扫描模式打包（扫描所有文件，适用于正式发布）${NC}
  ./ci/package_install.sh all v1.0.0 --full

${GREEN}说明：${NC}
  - 打包的目录和文件名会自动包含时间戳（格式: YYYYMMDD_HHMMSS）
  - 默认全量包只记录 git 变更的文件（类似增量包逻辑）
  - 使用 --full 模式会扫描所有文件，生成完整的文件清单（适用于正式发布）
  - 使用 --from-commit 时，manifest 只包含从指定提交之后变更的文件
  - 输出目录默认根据操作系统自动选择：
    Windows: ../Package/（位于项目源码目录外）
    Linux/Mac: ~/Package/（位于用户主目录）
  - Manifest 文件保存在对应的打包目录下

${BLUE}==========================================${NC}
EOF
    exit 0
}

# 检查是否需要显示帮助
if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    show_help
fi

# 解析参数
PACKAGE_TYPE=${1:-all}  # all 或 patch
VERSION=${2:-${VERSION:-dev}}
BASE_VERSION=""  # 基准版本号（仅 patch 模式需要）
FROM_COMMIT=""  # 从指定提交记录开始计算manifest
FULL_SCAN="false"  # 完整扫描模式：扫描所有文件（默认只扫描 git 变更的文件）

# 解析剩余参数
shift 2 2>/dev/null || true
while [[ $# -gt 0 ]]; do
    case $1 in
        --from-commit)
            FROM_COMMIT="$2"
            if [ -z "$FROM_COMMIT" ]; then
                log_error "--from-commit 参数需要指定提交记录"
                exit 1
            fi
            shift 2
            ;;
        --full)
            FULL_SCAN="true"
            log_info "启用完整扫描模式：扫描所有文件"
            shift
            ;;
        *)
            # 如果 BASE_VERSION 为空，可能是旧的参数格式（基准版本号）
            if [ -z "$BASE_VERSION" ] && [ "$PACKAGE_TYPE" = "patch" ]; then
                BASE_VERSION="$1"
            else
                log_warning "未知参数: $1"
            fi
            shift
            ;;
    esac
done

# 默认参数
GAME_DIR=${GAME_DIR:-LieRen}
BUILD_TYPE=${BUILD_TYPE:-DynamicDebug}

# 获取脚本所在目录和项目根目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# 检测操作系统类型
detect_os_type() {
    if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ -n "${WINDIR:-}" ]] || [[ "$OSTYPE" == "win32" ]]; then
        echo "windows"
    else
        echo "linux"
    fi
}

OS_TYPE=$(detect_os_type)

# 根据操作系统设置默认路径（如果用户未通过环境变量指定）
if [ -z "${INSTALL_DIR:-}" ]; then
    if [ "$OS_TYPE" = "windows" ]; then
        # Windows: 使用相对路径，放到项目源码目录外
        INSTALL_DIR="../PackageTemp"
    else
        # Linux/Mac: 直接使用 $HOME，避免 ~ 展开问题
        INSTALL_DIR="${HOME}/PackageTemp"
    fi
fi

if [ -z "${OUTPUT_DIR:-}" ]; then
    if [ "$OS_TYPE" = "windows" ]; then
        # Windows: 使用相对路径，放到项目源码目录外
        OUTPUT_DIR="../Package"
    else
        # Linux/Mac: 直接使用 $HOME，避免 ~ 展开问题
        OUTPUT_DIR="${HOME}/Package"
    fi
fi

# 展开路径中的 ~ 为用户主目录（如果存在）
# 无论路径是否以 / 开头，都要检查并展开 ~
if [[ "$INSTALL_DIR" == *"~"* ]]; then
    INSTALL_DIR="${INSTALL_DIR//\~/$HOME}"
fi

if [[ "$OUTPUT_DIR" == *"~"* ]]; then
    OUTPUT_DIR="${OUTPUT_DIR//\~/$HOME}"
fi

# 确保 INSTALL_DIR 是绝对路径
if [ "${INSTALL_DIR#/}" = "$INSTALL_DIR" ]; then
    if [ "${INSTALL_DIR#../}" != "$INSTALL_DIR" ]; then
        # 以 ../ 开头，相对于项目根目录的父目录（放到源码目录外）
        INSTALL_DIR="$(cd "$PROJECT_ROOT/.." && pwd)/${INSTALL_DIR#../}"
    else
        # 相对路径，转换为绝对路径（相对于项目根目录）
        INSTALL_DIR="${PROJECT_ROOT}/${INSTALL_DIR}"
    fi
fi

# 确保 OUTPUT_DIR 是绝对路径
if [ "${OUTPUT_DIR#/}" = "$OUTPUT_DIR" ]; then
    if [ "${OUTPUT_DIR#../}" != "$OUTPUT_DIR" ]; then
        # 以 ../ 开头，相对于项目根目录的父目录（放到源码目录外）
        OUTPUT_DIR="$(cd "$PROJECT_ROOT/.." && pwd)/${OUTPUT_DIR#../}"
    else
        # 相对路径，转换为绝对路径（相对于项目根目录）
        OUTPUT_DIR="${PROJECT_ROOT}/${OUTPUT_DIR}"
    fi
fi

# 生成时间戳（精确到秒）
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')

# 验证参数
if [ "$PACKAGE_TYPE" != "all" ] && [ "$PACKAGE_TYPE" != "patch" ]; then
    log_error "无效的打包类型: $PACKAGE_TYPE"
    log_error "请使用 'all' 或 'patch'"
    exit 1
fi

if [ "$PACKAGE_TYPE" = "patch" ] && [ -z "$BASE_VERSION" ]; then
    log_error "增量包模式需要指定基准版本号"
    log_error "使用方法: $0 patch <当前版本号> <基准版本号>"
    exit 1
fi

log_info "=========================================="
log_info "NFShmXFrame 打包脚本（使用直接复制）"
log_info "=========================================="
log_info "打包类型: ${PACKAGE_TYPE}"
log_info "项目: ${GAME_DIR}"
log_info "构建类型: ${BUILD_TYPE}"
log_info "版本: ${VERSION}"
if [ "$PACKAGE_TYPE" = "patch" ]; then
    log_info "基准版本: ${BASE_VERSION}"
fi
if [ -n "$FROM_COMMIT" ]; then
    log_info "从提交记录计算manifest: ${FROM_COMMIT}"
fi
log_info "时间戳: ${TIMESTAMP}"
log_info "安装目录: ${INSTALL_DIR}"
log_info "输出目录: ${OUTPUT_DIR}"
log_info "=========================================="

# 创建必要的目录
mkdir -p "${OUTPUT_DIR}"

# 清理输出目录中的旧内容（保留打包目录和压缩文件）
cleanup_output_dir() {
    log_info "清理输出目录中的临时文件..."
    
    if [ ! -d "${OUTPUT_DIR}" ]; then
        return
    fi
    
    # 删除临时文件（diff-*.txt, changed-files-*.txt, added-files-*.txt, modified-files-*.txt, deleted-files-*.txt, current-list-*.txt, base-list-*.txt）
    local file_count=0
    for file in "${OUTPUT_DIR}"/diff-*.txt "${OUTPUT_DIR}"/changed-files-*.txt "${OUTPUT_DIR}"/added-files-*.txt \
                "${OUTPUT_DIR}"/modified-files-*.txt "${OUTPUT_DIR}"/deleted-files-*.txt \
                "${OUTPUT_DIR}"/current-list-*.txt "${OUTPUT_DIR}"/base-list-*.txt; do
        if [ -f "$file" ]; then
            log_info "删除临时文件: $(basename "$file")"
            rm -f "$file"
            file_count=$((file_count + 1))
        fi
    done
    
    # 统计现有的打包目录和文件
    local package_dirs=$(find "${OUTPUT_DIR}" -maxdepth 1 -type d -name "nfshm-xframe-*" 2>/dev/null | wc -l | tr -d ' ')
    local package_files=$(find "${OUTPUT_DIR}" -maxdepth 1 -type f \( -name "*.tar.gz" -o -name "*.sha256" \) 2>/dev/null | wc -l | tr -d ' ')
    
    # 显示清理结果
    if [ $file_count -gt 0 ]; then
        log_info "已清理: ${file_count} 个临时文件"
    else
        log_info "无需清理（没有临时文件）"
    fi
    
    # 显示保留的内容
    if [ "$package_dirs" -gt 0 ]; then
        log_info "保留打包目录: ${package_dirs} 个（包含 manifest.md）"
    fi
    if [ "$package_files" -gt 0 ]; then
        log_info "保留压缩文件: ${package_files} 个 (.tar.gz 和 .sha256)"
    fi
    
    log_success "输出目录清理完成"
}

# 清理旧的安装目录
cleanup_install_dir() {
    log_info "清理旧的安装目录: ${INSTALL_DIR}..."
    
    if [ -d "${INSTALL_DIR}" ]; then
        # 先统计目录中的文件数量
        local file_count=$(find "${INSTALL_DIR}" -type f 2>/dev/null | wc -l | tr -d ' ')
        local dir_count=$(find "${INSTALL_DIR}" -mindepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
        
        if [ "$file_count" -gt 0 ] || [ "$dir_count" -gt 0 ]; then
            log_info "发现 ${file_count} 个文件和 ${dir_count} 个目录，开始清理..."
            
            # 方法1: 尝试使用 find -delete（最彻底的方法）
            if find "${INSTALL_DIR}" -mindepth 1 -delete 2>/dev/null; then
                log_info "使用 find -delete 成功清理"
            else
                # 方法2: 如果 find -delete 失败，尝试删除目录并重建
                log_warning "使用 find -delete 删除失败，尝试删除整个目录..."
                rm -rf "${INSTALL_DIR}" 2>/dev/null || {
                    # 方法3: 如果删除目录失败，尝试逐个删除内容
                    log_warning "删除目录失败，尝试逐个删除内容..."
                    # 删除所有可见文件
                    rm -rf "${INSTALL_DIR}"/* 2>/dev/null || true
                    # 删除隐藏文件（.开头的文件，但不包括 . 和 ..）
                    find "${INSTALL_DIR}" -mindepth 1 -name ".*" -delete 2>/dev/null || true
                }
            fi
            
            # 验证是否清理干净
            local remaining=$(find "${INSTALL_DIR}" -mindepth 1 2>/dev/null | wc -l | tr -d ' ')
            if [ "$remaining" -gt 0 ]; then
                log_warning "仍有 ${remaining} 个项目未删除，强制删除目录并重建..."
                rm -rf "${INSTALL_DIR}" 2>/dev/null || true
            fi
        else
            log_info "目录为空，无需清理"
        fi
    fi
    
    # 确保目录存在且为空
    mkdir -p "${INSTALL_DIR}"
    
    # 验证目录确实为空
    local verify_count=$(find "${INSTALL_DIR}" -mindepth 1 2>/dev/null | wc -l | tr -d ' ')
    if [ "$verify_count" -eq 0 ]; then
        log_success "安装目录已清理完成: ${INSTALL_DIR} (已清空)"
    else
        log_warning "安装目录清理后仍有 ${verify_count} 个项目:"
        find "${INSTALL_DIR}" -mindepth 1 2>/dev/null | head -10 | while read -r item; do
            log_warning "  - ${item}"
        done
        if [ "$verify_count" -gt 10 ]; then
            log_warning "  ... 还有 $((verify_count - 10)) 个项目"
        fi
        log_warning "继续执行，但可能影响打包结果"
    fi
}

# 直接复制文件进行安装
copy_files_directly() {
    log_info "使用直接复制方式安装文件..."
    
    # 创建目标目录结构
    mkdir -p "${INSTALL_DIR}/Bin/${BUILD_TYPE}"
    mkdir -p "${INSTALL_DIR}/Config/${GAME_DIR}"
    mkdir -p "${INSTALL_DIR}/Plugin/${GAME_DIR}"
    mkdir -p "${INSTALL_DIR}/LuaScript/${GAME_DIR}"
    
    # 复制二进制文件
    if [ -d "Install/Bin/${BUILD_TYPE}" ]; then
        log_info "复制二进制文件从 Install/Bin/${BUILD_TYPE} 到 ${INSTALL_DIR}/Bin/${BUILD_TYPE}/"
        cp -r "Install/Bin/${BUILD_TYPE}"/* "${INSTALL_DIR}/Bin/${BUILD_TYPE}/" 2>/dev/null || true
    else
        log_warning "Install/Bin/${BUILD_TYPE} 目录不存在，跳过二进制文件复制"
    fi
    
    # 复制配置文件
    if [ -d "Install/Config/${GAME_DIR}" ]; then
        log_info "复制配置文件从 Install/Config/${GAME_DIR} 到 ${INSTALL_DIR}/Config/${GAME_DIR}/"
        cp -r "Install/Config/${GAME_DIR}"/* "${INSTALL_DIR}/Config/${GAME_DIR}/" 2>/dev/null || true
    else
        log_warning "Install/Config/${GAME_DIR} 目录不存在，跳过配置文件复制"
    fi
    
    # 复制插件配置
    if [ -d "Install/Plugin/${GAME_DIR}" ]; then
        log_info "复制插件配置从 Install/Plugin/${GAME_DIR} 到 ${INSTALL_DIR}/Plugin/${GAME_DIR}/"
        cp -r "Install/Plugin/${GAME_DIR}"/* "${INSTALL_DIR}/Plugin/${GAME_DIR}/" 2>/dev/null || true
    else
        log_warning "Install/Plugin/${GAME_DIR} 目录不存在，跳过插件配置复制"
    fi
    
    # 复制 Lua 脚本
    if [ -d "Install/LuaScript/${GAME_DIR}" ]; then
        log_info "复制 Lua 脚本从 Install/LuaScript/${GAME_DIR} 到 ${INSTALL_DIR}/LuaScript/${GAME_DIR}/"
        cp -r "Install/LuaScript/${GAME_DIR}"/* "${INSTALL_DIR}/LuaScript/${GAME_DIR}/" 2>/dev/null || true
    else
        log_warning "Install/LuaScript/${GAME_DIR} 目录不存在，跳过 Lua 脚本复制"
    fi
    
    # 复制 NFServerController
    if [ -f "tools/NFServerController.exe" ]; then
        log_info "复制 NFServerController.exe 到 ${INSTALL_DIR}/"
        cp "tools/NFServerController.exe" "${INSTALL_DIR}/" 2>/dev/null || true
    elif [ -f "tools/NFServerController" ]; then
        log_info "复制 NFServerController 到 ${INSTALL_DIR}/"
        cp "tools/NFServerController" "${INSTALL_DIR}/" 2>/dev/null || true
        chmod +x "${INSTALL_DIR}/NFServerController" 2>/dev/null || true
    else
        log_warning "NFServerController 不存在，跳过"
    fi
    
    # 复制 linux_servers.conf
    if [ -f "Install/linux_servers.conf" ]; then
        log_info "复制 linux_servers.conf 到 ${INSTALL_DIR}/"
        cp "Install/linux_servers.conf" "${INSTALL_DIR}/" 2>/dev/null || true
    else
        log_warning "Install/linux_servers.conf 不存在，跳过"
    fi
    
    log_success "直接复制完成"
}

# 判断文件是否为配置文件
# 参数: $1 - 文件相对路径
# 返回: 0=配置文件, 1=源码文件
is_config_file() {
    local file_path="$1"
    # 配置文件：Bin目录、Config目录、Plugin目录下的文件
    if [[ "$file_path" == Bin/* ]] || \
       [[ "$file_path" == Config/* ]] || \
       [[ "$file_path" == */Config/* ]] || \
       [[ "$file_path" == Plugin/* ]] || \
       [[ "$file_path" == */Plugin/* ]]; then
        return 0
    fi
    return 1
}

# 判断文件是否为源码文件
# 参数: $1 - 文件相对路径
# 返回: 0=源码文件, 1=其他
is_source_file() {
    local file_path="$1"
    # 源码文件：C++/C (.cpp, .c, .h, .hpp, .cc, .cxx)
    # Lua (.lua) - 包括 LuaScript 目录下的 Lua 脚本
    # Python (.py, .pyc, .pyo)
    case "$file_path" in
        *.cpp|*.c|*.h|*.hpp|*.cc|*.cxx|*.lua|*.py|*.pyc|*.pyo)
            return 0
            ;;
        LuaScript/*.lua|*/LuaScript/*.lua)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# 获取文件的 Git 信息
# 参数: $1 - 文件路径（相对于项目根目录）
# 返回: commit_hash author (空格分隔)
get_git_info() {
    local file_path="$1"
    local commit_hash="unknown"
    local author="unknown"
    
    # 尝试从 Git 仓库获取信息（优化：一次调用获取两个信息）
    if git rev-parse --git-dir >/dev/null 2>&1; then
        # 一次性获取 commit hash 和 author（性能优化：减少 50% 的 git 调用）
        local git_info=$(git log -1 --format="%H %an" -- "$file_path" 2>/dev/null || echo "unknown unknown")
        commit_hash=$(echo "$git_info" | cut -d' ' -f1)
        author=$(echo "$git_info" | cut -d' ' -f2-)
        
        # 如果获取失败，设置为 unknown
        if [ -z "$commit_hash" ] || [ "$commit_hash" = "" ]; then
            commit_hash="unknown"
        fi
        if [ -z "$author" ] || [ "$author" = "" ]; then
            author="unknown"
        fi
    fi
    
    echo "${commit_hash} ${author}"
}

# 获取文件的 MD5 值
# 参数: $1 - 文件路径
# 返回: MD5 值
get_file_md5() {
    local file_path="$1"
    
    if command -v md5sum &> /dev/null; then
        md5sum "$file_path" 2>/dev/null | cut -d' ' -f1 || echo "unknown"
    elif command -v md5 &> /dev/null; then
        md5 -q "$file_path" 2>/dev/null || echo "unknown"
    else
        echo "unknown"
    fi
}

# 生成文件清单（包含文件路径、MD5、Git版本号、最后修改作者）
# 参数: $1 - 打包目录（manifest 文件将生成到此目录下）
generate_manifest() {
    local package_dir="$1"
    if [ -z "$package_dir" ]; then
        log_error "generate_manifest: 需要指定打包目录"
        exit 1
    fi
    
    # manifest 文件名不带时间戳（因为目录名已经包含时间戳）
    local manifest_file="${package_dir}/manifest.md"
    
    log_info "生成文件清单: ${manifest_file}"
    
    # 从打包目录名推断包名
    local package_name=$(basename "${package_dir}")
    local package_tgz="${package_name}.tar.gz"
    
    # 计算包的MD5（如果包已存在，打包文件在第一个目录下）
    local package_md5=""
    local package_tgz_path="${package_dir}/${package_tgz}"
    if [ -f "${package_tgz_path}" ]; then
        package_md5=$(get_file_md5 "${package_tgz_path}")
    else
        package_md5="待打包后计算"
    fi
    
    # 获取Git分支信息
    local git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
    
    # 确定发布模版
    local release_template=""
    if [ "$PACKAGE_TYPE" = "patch" ]; then
        release_template="【正式环境】不停机更新"
    else
        release_template="【正式环境】全量更新"
    fi
    
    # 获取git log信息（用于服务器更新清单）
    local git_log_info=""
    local base_commit=""
    if [ -n "$FROM_COMMIT" ]; then
        base_commit="$FROM_COMMIT"
    elif [ "$PACKAGE_TYPE" = "patch" ] && [ -n "$BASE_VERSION" ]; then
        # 尝试从BASE_VERSION找到对应的commit
        base_commit=$(git rev-parse "$BASE_VERSION" 2>/dev/null || echo "")
    fi
    
    if [ -n "$base_commit" ]; then
        # 获取从base_commit到HEAD的git log（包含base_commit）
        git_log_info=$(git log --pretty=format:"- %h - %an - %s" "${base_commit}^..HEAD" 2>/dev/null || \
                      git log --pretty=format:"- %h - %an - %s" --root "${base_commit}..HEAD" 2>/dev/null || echo "")
    else
        # 如果没有base_commit，获取最近的10条log
        git_log_info=$(git log --pretty=format:"- %h - %an - %s" -10 2>/dev/null || echo "")
    fi
    
    # 创建清单文件头部（参考图片格式）
    cat > "${manifest_file}" <<EOF
# NFShmXFrame 文件清单

## 【职能化发布参数】

| 项目 | 值 |
|------|-----|
| 发布模版 | ${release_template} |
| 包名 | \`${package_tgz}\` |
| 包MD5 | \`${package_md5}\` |
| 版本分支 | \`${git_branch}\` |

## 发布版本 & 备注

| 项目 | 值 |
|------|-----|
| 发布版本 | ${VERSION} |
| 备注 | - |

## 发布环境

| 项目 | 值 |
|------|-----|
| 环境 | 正式环境 |

## cvm补丁包信息

| 项目 | 值 |
|------|-----|
| 包名 | \`${package_tgz}\` |
| MD5 | \`${package_md5}\` |

## 服务器更新清单

$(if [ -n "$git_log_info" ]; then
    echo "$git_log_info"
else
    echo "*无更新记录*"
fi)

EOF
    
    # 创建临时文件用于分类
    local update_files_list=$(mktemp /tmp/update-files-list-$$.XXXXXX)  # 更新文件清单（不包含源码）
    local source_changed_list=$(mktemp /tmp/source-changed-list-$$.XXXXXX)  # 源码改动清单
    local cvm_package_list=$(mktemp /tmp/cvm-package-list-$$.XXXXXX)  # cvm补丁包文件清单
    local bin_files_list=$(mktemp /tmp/bin-files-list-$$.XXXXXX)  # bin文件列表
    
    # 默认模式（全量包和增量包）：处理 git log 中修改的文件
    # 完整扫描模式（--full）：跳过 git log 处理，直接扫描所有文件
    if [ "$FULL_SCAN" != "true" ]; then
        # 获取git log中修改的文件列表（默认模式：全量包和增量包都使用此逻辑）
        local git_changed_files=$(mktemp /tmp/git-changed-files-$$.XXXXXX)
        local git_base_commit=""
        
        if [ -n "$FROM_COMMIT" ]; then
            git_base_commit="$FROM_COMMIT"
        elif [ "$PACKAGE_TYPE" = "patch" ] && [ -n "$BASE_VERSION" ]; then
            # 尝试从BASE_VERSION找到对应的commit
            git_base_commit=$(git rev-parse "$BASE_VERSION" 2>/dev/null || echo "")
        fi
        
        if [ -n "$git_base_commit" ]; then
            log_info "从提交记录 ${git_base_commit} 开始获取git log变更文件（包含该提交）..."
            # 获取变更的文件（使用 ^..HEAD 包含指定提交本身）
            # 如果是第一个提交，使用 --root 选项
            if git diff --name-only --diff-filter=ACMR "${git_base_commit}^..HEAD" > "$git_changed_files" 2>/dev/null; then
                : # 成功
            else
                # 可能是第一个提交，尝试使用 --root
                git diff --name-only --diff-filter=ACMR --root "${git_base_commit}..HEAD" > "$git_changed_files" 2>/dev/null || true
            fi
            if [ -s "$git_changed_files" ]; then
                log_info "找到 $(wc -l < "$git_changed_files" | tr -d ' ') 个git变更的文件"
            fi
        else
            # 如果没有base commit，获取最近一次提交的变更文件
            log_info "获取最近一次提交的变更文件..."
            git diff --name-only --diff-filter=ACMR HEAD~1..HEAD > "$git_changed_files" 2>/dev/null || true
            if [ -s "$git_changed_files" ]; then
                log_info "找到 $(wc -l < "$git_changed_files" | tr -d ' ') 个git变更的文件"
            fi
        fi
        
        # 处理git log中修改的文件（仅增量包模式）
        if [ -s "$git_changed_files" ]; then
            log_info "处理git log中修改的文件..."
        while IFS= read -r git_file; do
            [ -z "$git_file" ] && continue
            
            # 检查是否为打包文件（Bin、Config、Plugin、LuaScript）
            # 注意：Install/Plugin/* 和 Install/LuaScript/* 下的文件（包括 .lua 文件）都应该包含在打包文件中
            local is_package_file=false
            local rel_path=""
            local actual_file=""
            
            if [[ "$git_file" == Install/Bin/* ]] || \
               [[ "$git_file" == Install/Config/* ]] || \
               [[ "$git_file" == Install/Plugin/* ]] || \
               [[ "$git_file" == Install/LuaScript/* ]]; then
                is_package_file=true
                rel_path="${git_file#Install/}"
                # 优先使用 PackageTemp 中的文件，如果不存在则使用 Install 目录中的文件
                actual_file="${INSTALL_DIR}/${rel_path}"
                if [ ! -f "$actual_file" ]; then
                    # 如果 PackageTemp 中不存在，使用 Install 目录中的原始文件
                    actual_file="$git_file"
                fi
            elif [[ "$git_file" == Bin/* ]] || \
                 [[ "$git_file" == Config/* ]] || \
                 [[ "$git_file" == Plugin/* ]] || \
                 [[ "$git_file" == LuaScript/* ]]; then
                is_package_file=true
                rel_path="$git_file"
                # 优先使用 PackageTemp 中的文件，如果不存在则使用 Install 目录中的文件
                actual_file="${INSTALL_DIR}/${rel_path}"
                if [ ! -f "$actual_file" ]; then
                    # 如果 PackageTemp 中不存在，尝试使用 Install 目录中的文件
                    local install_file="Install/${rel_path}"
                    if [ -f "$install_file" ]; then
                        actual_file="$install_file"
                    else
                        # 如果 Install/${rel_path} 也不存在，尝试直接使用 git_file（可能路径已经是正确的）
                        if [ -f "$git_file" ]; then
                            actual_file="$git_file"
                        else
                            actual_file=""
                        fi
                    fi
                fi
            fi
            
            if [ "$is_package_file" = true ]; then
                    # 检查文件是否存在（可能在 PackageTemp 或 Install 目录中）
                    # 在 Windows 下，路径可能需要特殊处理，所以先尝试多个可能的路径
                    local found_file=""
                    
                    # 尝试多个可能的路径（按优先级）
                    local possible_paths=(
                        "$actual_file"
                        "${INSTALL_DIR}/${rel_path}"
                        "Install/${rel_path}"
                        "$git_file"
                        "${rel_path}"
                    )
                    
                    # 首先尝试使用 git 检查文件（因为 git log 能返回这些文件，说明它们在 git 仓库中）
                    if git ls-files --error-unmatch "$git_file" >/dev/null 2>&1; then
                        # 文件在 git 仓库中，尝试找到实际文件路径
                        for path in "${possible_paths[@]}"; do
                            [ -z "$path" ] && continue
                            # 使用多种方法检查文件是否存在（Windows 兼容性）
                            # 方法1: test -f（最可靠的方法）
                            if [ -f "$path" ] 2>/dev/null; then
                                found_file="$path"
                                break
                            fi
                            # 方法2: 使用 test -r（检查文件是否可读）
                            if [ -r "$path" ] 2>/dev/null && [ ! -d "$path" ] 2>/dev/null; then
                                found_file="$path"
                                break
                            fi
                            # 方法3: 使用 stat 命令（如果可用）
                            if command -v stat >/dev/null 2>&1; then
                                if stat "$path" >/dev/null 2>&1 && [ ! -d "$path" ] 2>/dev/null; then
                                    found_file="$path"
                                    break
                                fi
                            fi
                            # 方法4: 使用 ls 命令（Windows Git Bash 兼容）
                            if ls -d "$path" >/dev/null 2>&1 && [ ! -d "$path" ] 2>/dev/null; then
                                found_file="$path"
                                break
                            fi
                        done
                        
                        # 如果所有方法都失败，但文件在 git 中，直接使用 git_file 路径
                        if [ -z "$found_file" ] && [ -f "$git_file" ] 2>/dev/null; then
                            found_file="$git_file"
                        fi
                    else
                        # 文件不在 git 仓库中，使用常规方法检查
                        for path in "${possible_paths[@]}"; do
                            [ -z "$path" ] && continue
                            if [ -f "$path" ] 2>/dev/null; then
                                found_file="$path"
                                break
                            fi
                        done
                    fi
                    
                    # 如果文件在 git 中但找不到实际文件，尝试直接使用 git_file（可能文件在工作目录中）
                    if [ -z "$found_file" ] && git ls-files --error-unmatch "$git_file" >/dev/null 2>&1; then
                        # 文件在 git 中，尝试直接使用 git_file 路径
                        if [ -f "$git_file" ] 2>/dev/null || [ -r "$git_file" ] 2>/dev/null; then
                            found_file="$git_file"
                        fi
                    fi
                    
                    if [ -n "$found_file" ] && ([ -f "$found_file" ] 2>/dev/null || [ -r "$found_file" ] 2>/dev/null); then
                        # 打包文件：添加到更新文件清单和cvm补丁包文件清单
                        # 注意：Install/Plugin/* 和 Install/LuaScript/* 下的文件（包括 .lua 文件）都应该包含在 cvm补丁包文件清单中
                        local md5_value=$(get_file_md5 "$found_file")
                        # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                        local git_info=$(get_git_info "$git_file")
                        local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                        local author=$(echo "$git_info" | cut -d' ' -f2-)
                        
                        # 添加到 cvm补丁包文件清单（包含所有打包文件）
                        echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
                        
                        # 添加到更新文件清单（排除 Install/LuaScript/ 下的文件，这些是代码）
                        if [[ "$git_file" != Install/LuaScript/* ]] && [[ "$git_file" != */LuaScript/* ]]; then
                            echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                        fi
                        
                        # 如果是源码文件（如 .lua），也需要添加到源码改动清单
                        # 但排除 Install/Plugin/ 下的文件（这些是配置，不是源码）
                        if is_source_file "$git_file" && [[ "$git_file" != Install/Plugin/* ]] && [[ "$git_file" != */Plugin/* ]]; then
                            echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
                        fi
                    else
                        # 尝试多个可能的路径
                        local tried_paths="${INSTALL_DIR}/${rel_path}, Install/${rel_path}, ${git_file}"
                        log_warning "  文件不存在，跳过: ${rel_path} (git路径: ${git_file}, 尝试路径: ${tried_paths})"
                        # 调试信息：列出所有尝试的路径
                        for path in "${possible_paths[@]}"; do
                            [ -z "$path" ] && continue
                            if [ -f "$path" ] 2>/dev/null; then
                                log_info "    路径存在但未匹配: $path"
                            elif [ -d "$path" ] 2>/dev/null; then
                                log_info "    路径是目录: $path"
                            else
                                log_info "    路径不存在: $path"
                            fi
                        done
                    fi
                elif is_source_file "$git_file"; then
                    # 其他源码文件（不在打包目录中的）：添加到源码改动清单
                    # 但排除 Install/Plugin/ 下的文件（这些是配置，不是源码）
                    if [[ "$git_file" != Install/Plugin/* ]] && [[ "$git_file" != */Plugin/* ]]; then
                        local actual_file="$git_file"
                        if [ -f "$actual_file" ]; then
                            local md5_value=$(get_file_md5 "$actual_file")
                            # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                            local git_info=$(get_git_info "$git_file")
                            local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                            local author=$(echo "$git_info" | cut -d' ' -f2-)
                            # 检查是否已存在（避免重复）
                            if ! grep -q "^${git_file} " "$source_changed_list" 2>/dev/null; then
                                echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
                            fi
                        fi
                    fi
                elif [ -f "$git_file" ] && ! is_source_file "$git_file"; then
                    # 其他非源码文件：添加到更新文件清单（排除 Install/LuaScript/ 下的文件，这些是代码）
                    if [[ "$git_file" != Install/LuaScript/* ]] && [[ "$git_file" != */LuaScript/* ]]; then
                        local md5_value=$(get_file_md5 "$git_file")
                        # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                        local git_info=$(get_git_info "$git_file")
                        local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                        local author=$(echo "$git_info" | cut -d' ' -f2-)
                        echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                    fi
                fi
        done < "$git_changed_files"
        fi
        # 清理 git_changed_files 临时文件
        rm -f "$git_changed_files"
    fi
    
    # 处理安装目录中的文件
    # 完整扫描模式（--full）：扫描所有目录的所有文件（Bin、Config、Plugin、LuaScript）
    # 默认模式：Bin 目录扫描所有文件，Config/Plugin/LuaScript 只处理 git log 中变更的
    if [ "$FULL_SCAN" = "true" ]; then
        # 完整扫描：扫描所有目录的所有文件（不依赖 git log）
        log_info "处理安装目录中的所有文件（完整扫描模式，扫描所有文件）..."
        log_info "安装目录: ${INSTALL_DIR}"
        local dirs_to_scan=("Bin" "Config" "Plugin" "LuaScript")
        local total_files=0
        for dir_name in "${dirs_to_scan[@]}"; do
            local install_dir_path="${INSTALL_DIR}/${dir_name}"
            if [ -d "$install_dir_path" ]; then
                log_info "扫描目录: ${install_dir_path}"
                local dir_file_count=0
                # 使用 process substitution 避免子 shell 问题
                while IFS= read -r file; do
                    [ -z "$file" ] && continue
                    [ ! -f "$file" ] && continue
                    local rel_path="${file#${INSTALL_DIR}/}"
                    local md5_value=$(get_file_md5 "$file")
                    if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                        log_warning "无法获取文件 MD5，跳过: ${file}"
                        continue
                    fi
                    local git_path="Install/${rel_path}"
                    # 优化：使用 get_git_info 函数一次性获取 commit 和 author（减少 50% 的 git 调用）
                    local git_info=$(get_git_info "$git_path")
                    local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                    local author=$(echo "$git_info" | cut -d' ' -f2-)
                    
                    # 添加到cvm补丁包文件清单（所有打包文件）
                    if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                        echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
                        dir_file_count=$((dir_file_count + 1))
                        total_files=$((total_files + 1))
                    fi
                    
                    # 添加到更新文件清单（排除 LuaScript/ 下的文件，这些是代码）
                    if [[ "$rel_path" != LuaScript/* ]] && [[ "$rel_path" != */LuaScript/* ]]; then
                        if ! grep -q "^${rel_path} " "$update_files_list" 2>/dev/null; then
                            echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                        fi
                    fi
                    
                    # 如果是源码文件，添加到源码改动清单（排除 Plugin/ 下的文件，这些是配置）
                    if is_source_file "$rel_path" && [[ "$rel_path" != Plugin/* ]] && [[ "$rel_path" != */Plugin/* ]]; then
                        if ! grep -q "^${rel_path} " "$source_changed_list" 2>/dev/null; then
                            echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
                        fi
                    fi
                    
                    # Bin 文件单独记录
                    if [[ "$rel_path" == Bin/* ]]; then
                        if ! grep -q "^${rel_path} " "$bin_files_list" 2>/dev/null; then
                            echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$bin_files_list"
                        fi
                    fi
                done < <(find "$install_dir_path" -type f 2>/dev/null | sort)
                log_info "  目录 ${dir_name}: 找到 ${dir_file_count} 个文件"
            else
                log_warning "目录不存在: ${install_dir_path}"
            fi
        done
        log_success "完整扫描模式：共扫描到 ${total_files} 个文件"
    else
        # 默认模式：Bin 目录扫描所有文件，Config/Plugin/LuaScript 只处理 git log 中变更的
        if [ -d "${INSTALL_DIR}/Bin" ]; then
            log_info "处理安装目录中的bin文件..."
            local bin_total_count=0
            local bin_so_count=0
            # 使用 process substitution 避免子 shell 问题
            while IFS= read -r file; do
                [ -z "$file" ] && continue
                local rel_path="${file#${INSTALL_DIR}/}"
                bin_total_count=$((bin_total_count + 1))
                
                # 统计 .so/.dll 文件
                if [[ "$rel_path" == *.so ]] || [[ "$rel_path" == *.so.* ]] || [[ "$rel_path" == *.dll ]]; then
                    bin_so_count=$((bin_so_count + 1))
                fi
                
                local md5_value=$(get_file_md5 "$file")
                if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                    log_warning "无法获取文件 MD5，跳过: ${rel_path}"
                    continue
                fi
                local git_path="Install/${rel_path}"
                # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                local git_info=$(get_git_info "$git_path")
                local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                local author=$(echo "$git_info" | cut -d' ' -f2-)
                
                # 添加到bin文件列表和更新文件清单
                echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$bin_files_list"
                
                # 检查是否已在update_files_list中（避免重复）
                if ! grep -q "^${rel_path} " "$update_files_list" 2>/dev/null; then
                    echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                fi
                
                # 添加到cvm补丁包文件清单（bin文件需要包含在cvm补丁包中）
                if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                    echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
                fi
            done < <(find "${INSTALL_DIR}/Bin" -type f 2>/dev/null | sort)
            log_info "  Bin 目录：共找到 ${bin_total_count} 个文件，其中 .so/.dll 文件 ${bin_so_count} 个"
        else
            log_warning "Bin 目录不存在: ${INSTALL_DIR}/Bin"
        fi
    fi
    
    # 写入更新文件清单（不包含源码：git log修改的非源码文件 + bin文件）
    echo "" >> "${manifest_file}"
    echo "## 更新文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$update_files_list" ]; then
        local update_count=$(wc -l < "$update_files_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        # 将文件列表转换为表格格式
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            # 解析行：文件路径 MD5 GIT版本号 最后修改作者
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            # 如果commit是unknown，显示为 ~
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$update_files_list"
        log_info "更新文件: ${update_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
        log_warning "未找到更新文件"
    fi
    
    # 写入源码改动清单（仅源码文件）
    echo "" >> "${manifest_file}"
    echo "## 源码改动清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$source_changed_list" ]; then
        local source_count=$(wc -l < "$source_changed_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        # 将文件列表转换为表格格式
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            # 解析行：文件路径 MD5 GIT版本号 最后修改作者
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            # 如果commit是unknown，显示为 ~
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$source_changed_list"
        log_info "源码文件: ${source_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无源码文件*" >> "${manifest_file}"
        log_warning "未找到源码文件"
    fi
    
    # 写入cvm补丁包文件清单（从git log中筛选出的打包文件，路径转换为projectx格式）
    echo "" >> "${manifest_file}"
    echo "## cvm补丁包文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$cvm_package_list" ]; then
        local cvm_count=$(wc -l < "$cvm_package_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        # 将文件列表转换为表格格式，路径转换为projectx格式
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            # 解析行：文件路径 MD5 GIT版本号 最后修改作者
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            
            # 保持原始相对路径，不做转换
            # cvm补丁包文件清单中，Git版本号和最后修改列为空（根据图片）
            echo "| \`${file_path}\` | \`${md5_val}\` |  |  |" >> "${manifest_file}"
        done < "$cvm_package_list"
        
        # 添加版本文件（如果存在，版本文件在第二个目录下）
        local package_name=$(basename "${package_dir}")
        local version_file="${package_dir}/${package_name}/VERSION.txt"
        if [ -f "${version_file}" ]; then
            local version_file_md5=$(get_file_md5 "${version_file}")
            # 保持原始相对路径
            echo "| \`VERSION.txt\` | \`${version_file_md5}\` |  |  |" >> "${manifest_file}"
        fi
        
        log_info "cvm补丁包文件: ${cvm_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
        log_warning "未找到cvm补丁包文件"
    fi
    
    # 清理临时文件
    rm -f "$update_files_list" "$source_changed_list" "$cvm_package_list" "$bin_files_list"
    # git_changed_files 只在非完整扫描模式下存在
    if [ "$FULL_SCAN" != "true" ]; then
        rm -f "$git_changed_files"
    fi
    
    # 判断是否为临时目录（用于对比）
    if [[ "${package_dir}" == /tmp/* ]]; then
        log_success "文件清单已生成（临时，用于对比）: ${manifest_file}"
    else
        log_success "文件清单已生成: ${manifest_file}"
    fi
    echo "${manifest_file}"
}

# 生成增量包的文件清单（只包含变更的文件）
# 参数: $1 - 打包目录（manifest 文件将生成到此目录下）
#       $2 - 变更文件列表文件路径
generate_patch_manifest() {
    local package_dir="$1"
    local changed_files="$2"
    
    if [ -z "$package_dir" ]; then
        log_error "generate_patch_manifest: 需要指定打包目录" >&2
        exit 1
    fi
    
    if [ -z "$changed_files" ] || [ ! -f "$changed_files" ]; then
        log_error "generate_patch_manifest: 变更文件列表不存在" >&2
        exit 1
    fi
    
    # manifest 文件名不带时间戳（因为目录名已经包含时间戳）
    local manifest_file="${package_dir}/manifest.md"
    
    log_info "生成增量包文件清单（仅变更文件）: ${manifest_file}" >&2
    
    # 从打包目录名推断包名
    local package_name=$(basename "${package_dir}")
    local package_tgz="${package_name}.tar.gz"
    
    # 计算包的MD5（如果包已存在，打包文件在第一个目录下）
    local package_md5=""
    local package_tgz_path="${package_dir}/${package_tgz}"
    if [ -f "${package_tgz_path}" ]; then
        package_md5=$(get_file_md5 "${package_tgz_path}")
    else
        package_md5="待打包后计算"
    fi
    
    # 获取Git分支信息
    local git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
    local current_commit=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
    
    # 增量包的发布模版
    local release_template="【正式环境】不停机更新"
    
    # 获取git log信息（用于服务器更新清单）
    local git_log_info=""
    local base_commit=""
    local commit_range_info=""
    
    if [ -n "$FROM_COMMIT" ]; then
        base_commit="$FROM_COMMIT"
        commit_range_info="从 ${FROM_COMMIT} 到 ${current_commit}"
    elif [ "$PACKAGE_TYPE" = "patch" ] && [ -n "$BASE_VERSION" ]; then
        # 尝试从BASE_VERSION找到对应的commit
        base_commit=$(git rev-parse "$BASE_VERSION" 2>/dev/null || echo "")
        if [ -n "$base_commit" ]; then
            commit_range_info="从 ${base_commit} (${BASE_VERSION}) 到 ${current_commit}"
        fi
    fi
    
    if [ -n "$base_commit" ]; then
        log_info "生成服务器更新清单：从 ${base_commit} 到 HEAD..." >&2
        
        # 使用 --first-parent 只显示主线上的提交，不包含合并进来的分支提交
        # ${base_commit}^..HEAD 表示从 base_commit（包含）到 HEAD
        local git_log_between=$(git log --first-parent --pretty=format:"- %h - %an - %s" "${base_commit}^..HEAD" 2>/dev/null || echo "")
        
        # 如果失败（base_commit 可能是第一个提交），尝试不使用 ^
        if [ -z "$git_log_between" ]; then
            git_log_between=$(git log --first-parent --pretty=format:"- %h - %an - %s" "${base_commit}..HEAD" 2>/dev/null || echo "")
            
            # 如果还是失败，尝试不使用 --first-parent（向后兼容）
            if [ -z "$git_log_between" ]; then
                git_log_between=$(git log --pretty=format:"- %h - %an - %s" "${base_commit}^..HEAD" 2>/dev/null || \
                                 git log --pretty=format:"- %h - %an - %s" "${base_commit}..HEAD" 2>/dev/null || echo "")
            fi
            
            # 手动添加 base_commit 信息
            local base_commit_info=$(git log --pretty=format:"- %h - %an - %s" -1 "${base_commit}" 2>/dev/null || echo "")
            if [ -n "$git_log_between" ] && [ -n "$base_commit_info" ]; then
                git_log_info="${git_log_between}"$'\n'"${base_commit_info}"
            elif [ -n "$git_log_between" ]; then
                git_log_info="${git_log_between}"
            else
                git_log_info="${base_commit_info}"
            fi
        else
            git_log_info="${git_log_between}"
        fi
        
        local commit_count=$(echo "$git_log_info" | grep -c '^-' 2>/dev/null || echo 0)
        log_info "服务器更新清单：从 ${base_commit} 到 HEAD，共 ${commit_count} 条记录（仅主线提交）" >&2
    else
        log_warning "未指定 from-commit，获取最近的10条log" >&2
        # 如果没有base_commit，获取最近的10条log
        git_log_info=$(git log --pretty=format:"- %h - %an - %s" -10 2>/dev/null || echo "")
    fi
    
    # 创建清单文件头部
    cat > "${manifest_file}" <<EOF
# NFShmXFrame 增量包文件清单

## 【职能化发布参数】

| 项目 | 值 |
|------|-----|
| 发布模版 | ${release_template} |
| 包名 | \`${package_tgz}\` |
| 包MD5 | \`${package_md5}\` |
| 版本分支 | \`${git_branch}\` |

## 发布版本 & 备注

| 项目 | 值 |
|------|-----|
| 发布版本 | ${VERSION} |
| 基准版本 | ${BASE_VERSION} |
| Commit 范围 | ${commit_range_info:-无} |
| 备注 | 增量包，只包含变更的文件 |

## 发布环境

| 项目 | 值 |
|------|-----|
| 环境 | 正式环境 |

## cvm补丁包信息

| 项目 | 值 |
|------|-----|
| 包名 | \`${package_tgz}\` |
| MD5 | \`${package_md5}\` |

## 服务器更新清单

$(if [ -n "$git_log_info" ]; then
    echo "$git_log_info"
else
    echo "*无更新记录*"
fi)

EOF
    
    # 创建临时文件用于分类
    local update_files_list=$(mktemp /tmp/update-files-list-$$.XXXXXX)  # 更新文件清单（不包含源码）
    local source_changed_list=$(mktemp /tmp/source-changed-list-$$.XXXXXX)  # 源码改动清单
    local cvm_package_list=$(mktemp /tmp/cvm-package-list-$$.XXXXXX)  # cvm补丁包文件清单
    
    # 获取git log中变更的文件列表
    local git_changed_files=$(mktemp /tmp/git-changed-files-$$.XXXXXX)
    
    if [ -n "$base_commit" ]; then
        log_info "从提交记录 ${base_commit} 开始获取git log变更文件（包含该提交）..." >&2
        # 获取变更的文件（使用 ^..HEAD 包含指定提交本身）
        if git diff --name-only --diff-filter=ACMR "${base_commit}^..HEAD" > "$git_changed_files" 2>/dev/null; then
            : # 成功
        else
            # 可能是第一个提交，尝试使用 --root
            git diff --name-only --diff-filter=ACMR --root "${base_commit}..HEAD" > "$git_changed_files" 2>/dev/null || true
        fi
        if [ -s "$git_changed_files" ]; then
            log_info "找到 $(wc -l < "$git_changed_files" | tr -d ' ') 个git变更的文件" >&2
        fi
    else
        # 如果没有base commit，获取最近一次提交的变更文件
        log_info "获取最近一次提交的变更文件..." >&2
        git diff --name-only --diff-filter=ACMR HEAD~1..HEAD > "$git_changed_files" 2>/dev/null || true
    fi
    
    # 处理git log中修改的文件
    log_info "处理git log中修改的文件..." >&2
    while IFS= read -r git_file; do
        [ -z "$git_file" ] && continue
        
        # 检查是否为打包文件（Bin、Config、Plugin、LuaScript）
        # 注意：Install/Plugin/* 和 Install/LuaScript/* 下的文件（包括 .lua 文件）都应该包含在打包文件中
        local is_package_file=false
        local rel_path=""
        local actual_file=""
        
        if [[ "$git_file" == Install/Bin/* ]] || \
           [[ "$git_file" == Install/Config/* ]] || \
           [[ "$git_file" == Install/Plugin/* ]] || \
           [[ "$git_file" == Install/LuaScript/* ]]; then
            is_package_file=true
            rel_path="${git_file#Install/}"
            actual_file="${INSTALL_DIR}/${rel_path}"
            
            if [ ! -f "$actual_file" ]; then
                actual_file="$git_file"
            fi
        elif [[ "$git_file" == Bin/* ]] || \
             [[ "$git_file" == Config/* ]] || \
             [[ "$git_file" == Plugin/* ]] || \
             [[ "$git_file" == LuaScript/* ]]; then
            is_package_file=true
            rel_path="$git_file"
            actual_file="${INSTALL_DIR}/${rel_path}"
            
            if [ ! -f "$actual_file" ]; then
                local install_file="Install/${rel_path}"
                if [ -f "$install_file" ]; then
                    actual_file="$install_file"
                else
                    actual_file="$git_file"
                fi
            fi
        fi
        
        if [ "$is_package_file" = true ] && [ -f "$actual_file" ]; then
            # 打包文件：添加到更新文件清单和cvm补丁包文件清单
            local md5_value=$(get_file_md5 "$actual_file")
            # 优化：使用 get_git_info 函数一次性获取 commit 和 author
            local git_info=$(get_git_info "$git_file")
            local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
            local author=$(echo "$git_info" | cut -d' ' -f2-)
            
            # 检查是否已经在cvm_package_list中（避免重复）
            if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
            fi
            
            # 添加到更新文件清单（排除 Install/LuaScript/ 下的文件，这些是代码）
            if [[ "$git_file" != Install/LuaScript/* ]] && [[ "$git_file" != */LuaScript/* ]]; then
                if ! grep -q "^${rel_path} " "$update_files_list" 2>/dev/null; then
                    echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                fi
            fi
            
            # 如果是源码文件（如 .lua），也需要添加到源码改动清单
            # 但排除 Install/Plugin/ 下的文件（这些是配置，不是源码）
            if is_source_file "$git_file" && [[ "$git_file" != Install/Plugin/* ]] && [[ "$git_file" != */Plugin/* ]]; then
                echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
            fi
        elif is_source_file "$git_file"; then
            # 其他源码文件（不在 Install/ 目录下的）：只添加到源码改动清单
            # 但排除 Install/Plugin/ 下的文件（这些是配置，不是源码）
            if [[ "$git_file" != Install/Plugin/* ]] && [[ "$git_file" != */Plugin/* ]]; then
                local actual_file="$git_file"
                if [ -f "$actual_file" ]; then
                    local md5_value=$(get_file_md5 "$actual_file")
                    # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                    local git_info=$(get_git_info "$git_file")
                    local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                    local author=$(echo "$git_info" | cut -d' ' -f2-)
                    echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
                fi
            fi
        elif [ -f "$git_file" ] && ! is_source_file "$git_file"; then
            # 其他非源码文件：添加到更新文件清单（排除 Install/LuaScript/ 下的文件，这些是代码）
            if [[ "$git_file" != Install/LuaScript/* ]] && [[ "$git_file" != */LuaScript/* ]]; then
                local md5_value=$(get_file_md5 "$git_file")
                # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                local git_info=$(get_git_info "$git_file")
                local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                local author=$(echo "$git_info" | cut -d' ' -f2-)
                echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
            fi
        fi
    done < "$git_changed_files"
    
    # 处理MD5变更的文件（用于cvm补丁包文件清单，只包含实际要打包的文件）
    log_info "处理MD5变更的文件..." >&2
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        
        # 优先从 PackageTemp 目录获取文件，如果不存在则从 Install 目录获取
        local actual_file="${INSTALL_DIR}/${file}"
        if [ ! -f "$actual_file" ]; then
            # 如果 PackageTemp 中不存在，尝试从 Install 目录获取
            local install_file="Install/${file}"
            if [ -f "$install_file" ]; then
                actual_file="$install_file"
            else
                log_warning "变更文件不存在: ${file} (尝试路径: ${INSTALL_DIR}/${file}, Install/${file})" >&2
                continue
            fi
        fi
        
        # 获取文件信息
        local md5_value=$(get_file_md5 "$actual_file")
        local git_path="Install/${file}"
        # 优化：使用 get_git_info 函数一次性获取 commit 和 author
        local git_info=$(get_git_info "$git_path")
        local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
        local author=$(echo "$git_info" | cut -d' ' -f2-)
        
        # 添加到cvm补丁包文件清单（所有打包文件）
        if ! grep -q "^${file} " "$cvm_package_list" 2>/dev/null; then
            echo "${file} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
        fi
        
        # 添加到更新文件清单（排除 Install/LuaScript/ 下的文件，这些是代码）
        if ! is_source_file "$file" && [[ "$file" != LuaScript/* ]] && [[ "$file" != */LuaScript/* ]]; then
            if ! grep -q "^${file} " "$update_files_list" 2>/dev/null; then
                echo "${file} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
            fi
        fi
        
        # 如果是源码文件，添加到源码改动清单（排除 Install/Plugin/ 下的文件，这些是配置）
        if is_source_file "$file" && [[ "$file" != Plugin/* ]] && [[ "$file" != */Plugin/* ]]; then
            if ! grep -q "^${file} " "$source_changed_list" 2>/dev/null; then
                echo "${file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
            fi
        fi
    done < "${changed_files}"
    
    # 扫描 Bin 目录下的所有文件（包括 .so/.dll 和可执行文件），确保在 changed_files 中的文件被添加到 cvm补丁包文件清单
    # 这些文件可能不在 git log 中（因为是编译产物），但如果在 changed_files 中，需要确保被添加到 cvm_package_list
    if [ -d "${INSTALL_DIR}/Bin" ] && [ -f "${changed_files}" ]; then
        log_info "检查 Bin 目录下在 changed_files 中的文件（包括 .so/.dll）..." >&2
        local bin_file_count=0
        local bin_so_count=0
        # 使用 process substitution 避免子 shell 问题
        while IFS= read -r file; do
            [ -z "$file" ] && continue
            local rel_path="${file#${INSTALL_DIR}/}"
            
            # 只处理 Bin 目录下的文件
            if [[ "$rel_path" == Bin/* ]]; then
                # 检查文件是否在 changed_files 中
                if grep -q "^${rel_path}$" "${changed_files}" 2>/dev/null; then
                    # 统计 .so/.dll 文件
                    if [[ "$rel_path" == *.so ]] || [[ "$rel_path" == *.so.* ]] || [[ "$rel_path" == *.dll ]]; then
                        bin_so_count=$((bin_so_count + 1))
                    fi
                    
                    # 检查是否已经在 cvm_package_list 中（避免重复）
                    if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                        local md5_value=$(get_file_md5 "$file")
                        if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                            log_warning "无法获取文件 MD5，跳过: ${rel_path}" >&2
                            continue
                        fi
                        local git_path="Install/${rel_path}"
                        local git_info=$(get_git_info "$git_path")
                        local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                        local author=$(echo "$git_info" | cut -d' ' -f2-)
                        
                        # 添加到 cvm补丁包文件清单
                        echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
                        bin_file_count=$((bin_file_count + 1))
                        log_info "  添加 Bin 文件到 cvm补丁包文件清单: ${rel_path}" >&2
                    fi
                fi
            fi
        done < <(find "${INSTALL_DIR}/Bin" -type f 2>/dev/null | sort)
        if [ "$bin_file_count" -gt 0 ]; then
            log_info "Bin 目录下共添加 ${bin_file_count} 个文件到 cvm补丁包文件清单（其中 .so/.dll 文件 ${bin_so_count} 个）" >&2
        fi
    fi
    
    # 清理临时文件
    rm -f "$git_changed_files"
    
    # 写入更新文件清单
    echo "" >> "${manifest_file}"
    echo "## 更新文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$update_files_list" ]; then
        local update_count=$(wc -l < "$update_files_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$update_files_list"
        log_info "更新文件: ${update_count} 个" >&2
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
    fi
    
    # 写入源码改动清单
    echo "" >> "${manifest_file}"
    echo "## 源码改动清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$source_changed_list" ]; then
        local source_count=$(wc -l < "$source_changed_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$source_changed_list"
        log_info "源码文件: ${source_count} 个" >&2
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无源码文件*" >> "${manifest_file}"
    fi
    
    # 写入cvm补丁包文件清单
    echo "" >> "${manifest_file}"
    echo "## cvm补丁包文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$cvm_package_list" ]; then
        local cvm_count=$(wc -l < "$cvm_package_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            # cvm补丁包文件清单中，Git版本号和最后修改列为空（根据全量包格式）
            echo "| \`${file_path}\` | \`${md5_val}\` |  |  |" >> "${manifest_file}"
        done < "$cvm_package_list"
        
        # 添加版本文件（如果存在）
        local package_name=$(basename "${package_dir}")
        local version_file="${package_dir}/${package_name}/VERSION.txt"
        if [ -f "${version_file}" ]; then
            local version_file_md5=$(get_file_md5 "${version_file}")
            echo "| \`VERSION.txt\` | \`${version_file_md5}\` |  |  |" >> "${manifest_file}"
        fi
        
        log_info "cvm补丁包文件: ${cvm_count} 个" >&2
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
    fi
    
    # 清理临时文件
    rm -f "$update_files_list" "$source_changed_list" "$cvm_package_list"
    
    log_success "增量包文件清单已生成: ${manifest_file}" >&2
    echo "${manifest_file}"
}

# 查找基准版本的 all_manifest.md 文件
# 参数: $1 - 基准版本号
# 返回: all_manifest.md 文件路径
find_base_manifest() {
    local base_version="$1"
    local found_manifest=""
    local latest_dir=""
    local latest_time=0
    
    log_info "查找基准版本 ${base_version} 的清单文件..."
    log_info "查找目录: ${OUTPUT_DIR}"
    
    # 检查输出目录是否存在，如果不存在则尝试创建
    if [ ! -d "${OUTPUT_DIR}" ]; then
        log_warning "输出目录不存在: ${OUTPUT_DIR}，尝试创建..."
        mkdir -p "${OUTPUT_DIR}" 2>/dev/null || {
            log_error "无法创建输出目录: ${OUTPUT_DIR}"
            echo ""
            return
        }
    fi
    
    # 优先查找 all_manifest.md（新方案）
    # 1. 查找全量包：nfshm-xframe-${GAME_DIR}-${base_version}-all-*/all_manifest.md
    local all_pattern="nfshm-xframe-${GAME_DIR}-${base_version}-all-*"
    if [ -d "${OUTPUT_DIR}" ]; then
        log_info "查找全量包模式: ${all_pattern}"
        for dir in "${OUTPUT_DIR}"/${all_pattern}; do
            [ -e "$dir" ] || continue
            if [ -d "$dir" ]; then
                log_info "  检查目录: ${dir}"
                if [ -f "${dir}/all_manifest.md" ]; then
                    local dir_time=$(stat -f%m "$dir" 2>/dev/null || stat -c%Y "$dir" 2>/dev/null || echo "0")
                    log_info "    找到 all_manifest.md (时间: ${dir_time})"
                    if [ "$dir_time" -gt "$latest_time" ]; then
                        latest_time=$dir_time
                        latest_dir="$dir"
                        found_manifest="${dir}/all_manifest.md"
                    fi
                else
                    log_info "    未找到 all_manifest.md"
                fi
            fi
        done
    else
        log_warning "输出目录不存在: ${OUTPUT_DIR}"
    fi
    
    # 2. 查找增量包：nfshm-xframe-${GAME_DIR}-${base_version}-patch-from-*/all_manifest.md
    if [ -z "$found_manifest" ] && [ -d "${OUTPUT_DIR}" ]; then
        local patch_pattern="nfshm-xframe-${GAME_DIR}-${base_version}-patch-from-*"
        log_info "查找增量包模式: ${patch_pattern}"
        for dir in "${OUTPUT_DIR}"/${patch_pattern}; do
            [ -e "$dir" ] || continue
            if [ -d "$dir" ]; then
                log_info "  检查目录: ${dir}"
                if [ -f "${dir}/all_manifest.md" ]; then
                    local dir_time=$(stat -f%m "$dir" 2>/dev/null || stat -c%Y "$dir" 2>/dev/null || echo "0")
                    log_info "    找到 all_manifest.md (时间: ${dir_time})"
                    if [ "$dir_time" -gt "$latest_time" ]; then
                        latest_time=$dir_time
                        latest_dir="$dir"
                        found_manifest="${dir}/all_manifest.md"
                    fi
                else
                    log_info "    未找到 all_manifest.md"
                fi
            fi
        done
    fi
    
    # 向后兼容：如果没找到 all_manifest.md，尝试查找 manifest.md（旧方案）
    if [ -z "$found_manifest" ]; then
        log_warning "未找到 all_manifest.md，尝试查找 manifest.md（向后兼容）"
        local base_pattern="nfshm-xframe-${GAME_DIR}-${base_version}-all-*"
        if [ -d "${OUTPUT_DIR}" ]; then
            log_info "查找旧格式全量包: ${base_pattern}"
            for dir in "${OUTPUT_DIR}"/${base_pattern}; do
                [ -e "$dir" ] || continue
                if [ -d "$dir" ]; then
                    log_info "  检查目录: ${dir}"
                    if [ -f "${dir}/manifest.md" ]; then
                        local dir_time=$(stat -f%m "$dir" 2>/dev/null || stat -c%Y "$dir" 2>/dev/null || echo "0")
                        log_info "    找到 manifest.md (时间: ${dir_time})"
                        if [ "$dir_time" -gt "$latest_time" ]; then
                            latest_time=$dir_time
                            latest_dir="$dir"
                            found_manifest="${dir}/manifest.md"
                        fi
                    else
                        log_info "    未找到 manifest.md"
                    fi
                fi
            done
        fi
        
        # 如果还是没找到，尝试在 manifests 目录中查找（向后兼容）
        if [ -z "$found_manifest" ]; then
            local old_pattern_md="${OUTPUT_DIR}/manifests/manifest-${GAME_DIR}-${base_version}-*.md"
            local old_pattern_txt="${OUTPUT_DIR}/manifests/manifest-${GAME_DIR}-${base_version}-*.txt"
            for old_manifest in ${old_pattern_md} ${old_pattern_txt}; do
                [ -e "$old_manifest" ] || continue
                if [ -f "$old_manifest" ]; then
                    found_manifest="$old_manifest"
                    log_warning "在旧位置找到基准版本清单: ${found_manifest}"
                    break
                fi
            done
            
            if [ -z "$found_manifest" ]; then
                local old_manifest_md="${OUTPUT_DIR}/manifests/manifest-${GAME_DIR}-${base_version}.md"
                local old_manifest_txt="${OUTPUT_DIR}/manifests/manifest-${GAME_DIR}-${base_version}.txt"
                if [ -f "$old_manifest_md" ]; then
                    found_manifest="$old_manifest_md"
                    log_warning "在旧位置找到基准版本清单: ${found_manifest}"
                elif [ -f "$old_manifest_txt" ]; then
                    found_manifest="$old_manifest_txt"
                    log_warning "在旧位置找到基准版本清单: ${found_manifest}"
                fi
            fi
        fi
    fi
    
    if [ -n "$found_manifest" ]; then
        log_success "找到基准版本清单: ${found_manifest}"
    else
        log_warning "未找到基准版本 ${base_version} 的清单文件"
        log_warning "查找的路径："
        log_warning "  1. ${OUTPUT_DIR}/nfshm-xframe-${GAME_DIR}-${base_version}-all-*/all_manifest.md"
        log_warning "  2. ${OUTPUT_DIR}/nfshm-xframe-${GAME_DIR}-${base_version}-patch-from-*/all_manifest.md"
        log_warning "  3. ${OUTPUT_DIR}/nfshm-xframe-${GAME_DIR}-${base_version}-all-*/manifest.md (向后兼容)"
    fi
    
    echo "$found_manifest"
}

# 对比文件清单，找出变更的文件
compare_manifests() {
    local current_manifest="$1"
    local base_version="$2"
    local diff_file="${OUTPUT_DIR}/diff-${VERSION}-vs-${base_version}.txt"
    
    # 查找基准版本的 manifest
    local base_manifest=$(find_base_manifest "$base_version")
    
    log_info "=========================================="
    log_info "开始对比文件清单..."
    log_info "=========================================="
    log_info "当前版本清单: ${current_manifest}"
    log_info "基准版本: ${base_version}"
    
    if [ -z "$base_manifest" ] || [ ! -f "$base_manifest" ]; then
        log_error "基准版本清单不存在"
        log_error "请先打包基准版本 ${base_version} 的全量包或增量包，例如："
        log_error "  ./ci/package_install.sh all ${base_version}"
        log_error ""
        log_error "查找路径："
        log_error "  ${OUTPUT_DIR}/nfshm-xframe-${GAME_DIR}-${base_version}-all-*/all_manifest.md"
        log_error "  或: ${OUTPUT_DIR}/nfshm-xframe-${GAME_DIR}-${base_version}-patch-from-*/all_manifest.md"
        exit 1
    fi
    
    log_success "找到基准版本清单: ${base_manifest}"
    
    # 创建变更文件列表
    local changed_files="${OUTPUT_DIR}/changed-files-${VERSION}.txt"
    local added_files="${OUTPUT_DIR}/added-files-${VERSION}.txt"
    local modified_files="${OUTPUT_DIR}/modified-files-${VERSION}.txt"
    local deleted_files="${OUTPUT_DIR}/deleted-files-${VERSION}.txt"
    
    > "${changed_files}"
    > "${added_files}"
    > "${modified_files}"
    > "${deleted_files}"
    
    # 创建临时文件用于对比
    local current_list="${OUTPUT_DIR}/current-list-$$.txt"
    local base_list="${OUTPUT_DIR}/base-list-$$.txt"
    
    # 提取文件路径（只从 "cvm补丁包文件清单" 表格中提取）
    # 对于 Markdown 格式，需要找到 "## cvm补丁包文件清单" 章节，然后提取该表格中的文件
    # 从表格行中提取文件路径：| `文件路径` | `MD5` | ... |
    # 方法：提取第一个反引号内的内容（文件路径）
    
    # 从当前版本的manifest中提取cvm补丁包文件清单
    awk '
        /^## cvm补丁包文件清单/ { in_section=1; next }
        /^## / { in_section=0 }
        in_section && /^\| *`[^`]+` *\|/ {
            # 提取第一个反引号内的内容（文件路径）
            match($0, /\| *`([^`]+)`/, arr)
            if (arr[1] != "") print arr[1]
        }
    ' "${current_manifest}" | grep -v '^文件$' | grep -v '^$' | sort > "${current_list}"
    
    # 从基准版本的manifest中提取cvm补丁包文件清单
    awk '
        /^## cvm补丁包文件清单/ { in_section=1; next }
        /^## / { in_section=0 }
        in_section && /^\| *`[^`]+` *\|/ {
            # 提取第一个反引号内的内容（文件路径）
            match($0, /\| *`([^`]+)`/, arr)
            if (arr[1] != "") print arr[1]
        }
    ' "${base_manifest}" | grep -v '^文件$' | grep -v '^$' | sort > "${base_list}"
    
    # 统计文件数（调试信息）
    local current_file_count=$(wc -l < "${current_list}" 2>/dev/null | tr -d ' ')
    local base_file_count=$(wc -l < "${base_list}" 2>/dev/null | tr -d ' ')
    log_info "当前版本 cvm补丁包文件清单 中的文件数: ${current_file_count}"
    log_info "基准版本 cvm补丁包文件清单 中的文件数: ${base_file_count}"
    
    # 统计 Bin 目录下的文件（调试信息）
    local current_bin_count=$(grep -c "^Bin/" "${current_list}" 2>/dev/null || echo "0")
    local current_bin_so_count=$(grep -E "^Bin/.*\.(so|so\.|dll)" "${current_list}" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
    local base_bin_count=$(grep -c "^Bin/" "${base_list}" 2>/dev/null || echo "0")
    local base_bin_so_count=$(grep -E "^Bin/.*\.(so|so\.|dll)" "${base_list}" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
    log_info "当前版本 Bin 目录文件数: ${current_bin_count}（其中 .so/.dll: ${current_bin_so_count}）"
    log_info "基准版本 Bin 目录文件数: ${base_bin_count}（其中 .so/.dll: ${base_bin_so_count}）"
    
    # 显示前几个 Bin 目录下的文件及其 MD5（用于调试）
    log_info "显示前 5 个 Bin 目录文件的 MD5 对比："
    local bin_file_index=0
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        if [[ "$file" == Bin/* ]]; then
            bin_file_index=$((bin_file_index + 1))
            if [ "$bin_file_index" -le 5 ]; then
                # 提取当前版本的 MD5
                local current_md5=$(awk -v file="$file" '
                    BEGIN { in_section = 0; file_pattern = "`" file "`" }
                    /^## cvm补丁包文件清单/ { in_section = 1; next }
                    /^## / { in_section = 0 }
                    in_section && index($0, file_pattern) > 0 {
                        if (match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)) {
                            print arr[1]
                            exit
                        }
                    }
                ' "${current_manifest}")
                # 提取基准版本的 MD5
                local base_md5=$(awk -v file="$file" '
                    BEGIN { in_section = 0; file_pattern = "`" file "`" }
                    /^## cvm补丁包文件清单/ { in_section = 1; next }
                    /^## / { in_section = 0 }
                    in_section && index($0, file_pattern) > 0 {
                        if (match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)) {
                            print arr[1]
                            exit
                        }
                    }
                ' "${base_manifest}")
                if [ "$current_md5" = "$base_md5" ]; then
                    log_info "  ${file}: MD5 相同 (${current_md5})"
                else
                    log_info "  ${file}: MD5 不同 (当前: ${current_md5}, 基准: ${base_md5})"
                fi
            fi
        fi
    done < "${current_list}"
    
    # 如果文件数为0，显示警告和部分内容
    if [ "$current_file_count" -eq 0 ]; then
        log_warning "当前版本 cvm补丁包文件清单 为空！"
        log_info "显示 manifest.md 的 cvm补丁包文件清单 部分（前20行）："
        awk '/^## cvm补丁包文件清单/,/^## / {print}' "${current_manifest}" | head -20
    fi
    if [ "$base_file_count" -eq 0 ]; then
        log_warning "基准版本 cvm补丁包文件清单 为空！"
        log_info "显示 manifest.md 的 cvm补丁包文件清单 部分（前20行）："
        awk '/^## cvm补丁包文件清单/,/^## / {print}' "${base_manifest}" | head -20
    fi
    
    # 找出新增和修改的文件
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        
        if ! grep -q "^${file}$" "${base_list}"; then
            # 新增的文件
            echo "${file}" >> "${added_files}"
            echo "${file}" >> "${changed_files}"
        else
            # 检查文件是否被修改（比较校验和）
            # 从 Markdown 表格中提取 MD5（第二列）
            # 格式：| `文件路径` | `MD5` | `Git版本号` | 最后修改作者 |
            # 或：| `文件路径` | `MD5` |  |  |（cvm补丁包文件清单）
            # 需要提取第二个反引号内的内容（MD5）
            
            # 从当前版本的cvm补丁包文件清单中提取MD5
            # 使用更健壮的方法：先找到包含文件路径的行，再提取MD5
            current_checksum=$(awk -v file="$file" '
                BEGIN {
                    in_section = 0
                    file_pattern = "`" file "`"
                }
                /^## cvm补丁包文件清单/ { in_section = 1; next }
                /^## / { in_section = 0 }
                in_section && index($0, file_pattern) > 0 {
                    # 找到包含文件路径的行，提取第二个反引号内的内容（MD5）
                    # 格式：| `文件路径` | `MD5` |  |  |
                    if (match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)) {
                        print arr[1]
                    exit
                    }
                }
            ' "${current_manifest}")
            
            # 从基准版本的cvm补丁包文件清单中提取MD5
            base_checksum=$(awk -v file="$file" '
                BEGIN {
                    in_section = 0
                    file_pattern = "`" file "`"
                }
                /^## cvm补丁包文件清单/ { in_section = 1; next }
                /^## / { in_section = 0 }
                in_section && index($0, file_pattern) > 0 {
                    # 找到包含文件路径的行，提取第二个反引号内的内容（MD5）
                    if (match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)) {
                        print arr[1]
                    exit
                    }
                }
            ' "${base_manifest}")
            
            # 如果 MD5 提取失败，对于 Bin 目录下的文件，尝试直接计算 MD5 进行比较
            if [[ "$file" == Bin/* ]] && ([ -z "$current_checksum" ] || [ -z "$base_checksum" ]); then
                local current_file="${INSTALL_DIR}/${file}"
                local base_file=""
                # 尝试从基准版本的打包目录中找到文件
                local base_package_dir=$(dirname "${base_manifest}")
                # 基准版本的打包目录结构：base_package_dir/nfshm-xframe-*-all-*/Bin/...
                local base_package_content_dir=$(find "${base_package_dir}" -maxdepth 1 -type d -name "nfshm-xframe-*-all-*" | head -1)
                if [ -n "$base_package_content_dir" ] && [ -f "${base_package_content_dir}/${file}" ]; then
                    base_file="${base_package_content_dir}/${file}"
                elif [ -f "${base_package_dir}/${file}" ]; then
                    base_file="${base_package_dir}/${file}"
                elif [ -f "Install/${file}" ]; then
                    base_file="Install/${file}"
                fi
                
                if [ -f "$current_file" ]; then
                    if [ -z "$current_checksum" ]; then
                        current_checksum=$(get_file_md5 "$current_file")
                    fi
                    if [ -n "$base_file" ] && [ -f "$base_file" ] && [ -z "$base_checksum" ]; then
                        base_checksum=$(get_file_md5 "$base_file")
                    fi
                    if [ -n "$current_checksum" ] && [ -n "$base_checksum" ]; then
                        log_info "直接计算 Bin 文件 MD5: ${file} (当前: ${current_checksum}, 基准: ${base_checksum})"
                    fi
                fi
            fi
            
            # 调试输出（对于 Bin 目录下的文件）
            if [[ "$file" == Bin/* ]]; then
            if [ -z "$current_checksum" ] || [ -z "$base_checksum" ]; then
                    log_warning "无法提取 Bin 文件 ${file} 的MD5值（当前: ${current_checksum:-空}, 基准: ${base_checksum:-空}）"
                elif [ "$current_checksum" != "$base_checksum" ]; then
                    log_info "Bin 文件 MD5 变更: ${file} (当前: ${current_checksum}, 基准: ${base_checksum})"
                fi
            fi
            
            if [ -z "$current_checksum" ] || [ -z "$base_checksum" ]; then
                # 如果任一MD5为空，说明提取失败，跳过比较
                if [[ "$file" != Bin/* ]]; then
                log_warning "无法提取文件 ${file} 的MD5值，跳过比较"
                fi
                continue
            fi
            
            if [ "$current_checksum" != "$base_checksum" ]; then
                echo "${file}" >> "${modified_files}"
                echo "${file}" >> "${changed_files}"
            fi
        fi
    done < "${current_list}"
    
    # 找出删除的文件（只有在 git log 中明确删除的文件才应该被标记为删除）
    # 获取基准版本的 commit hash
    local base_commit=$(git rev-parse "${base_version}" 2>/dev/null || echo "")
    if [ -z "$base_commit" ] && [ -n "$FROM_COMMIT" ]; then
        base_commit="$FROM_COMMIT"
    fi
    
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        
        if ! grep -q "^${file}$" "${current_list}"; then
            # 检查文件是否在 git log 中被删除
            local is_deleted=false
            
            # 尝试多个可能的路径
            local git_paths=("Install/${file}" "${file}")
            for git_path in "${git_paths[@]}"; do
                # 检查文件在 git diff 中是否被删除（D 状态）
                if [ -n "$base_commit" ]; then
                    if git diff --name-only --diff-filter=D "${base_commit}^..HEAD" 2>/dev/null | grep -q "^${git_path}$"; then
                        is_deleted=true
                        break
                    fi
                fi
                # 检查文件在 git 中是否存在
                if ! git ls-files --error-unmatch "$git_path" >/dev/null 2>&1; then
                    # 文件在 git 中不存在，可能被删除了
                    # 但需要确认：如果文件在基准版本中存在，但在当前版本中不存在，且不在 git 中，才认为是删除
                    if [ -f "${INSTALL_DIR}/${file}" ] || [ -f "Install/${file}" ]; then
                        # 文件仍然存在，不应该标记为删除（可能只是没有被包含在当前版本的清单中）
                        is_deleted=false
                    else
                        # 文件不存在，可能是被删除了
                        is_deleted=true
                    fi
                    break
                fi
            done
            
            if [ "$is_deleted" = true ]; then
                # 文件确实被删除了
                echo "${file}" >> "${deleted_files}"
                echo "${file}" >> "${changed_files}"
            fi
        fi
    done < "${base_list}"
    
    # 清理临时文件
    rm -f "${current_list}" "${base_list}"
    
    # 生成对比报告
    cat > "${diff_file}" <<EOF
# 版本对比报告
# 当前版本: ${VERSION}
# 基准版本: ${BASE_VERSION}
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')

## 新增文件 ($(wc -l < "${added_files}" | tr -d ' '))
$(cat "${added_files}")

## 修改文件 ($(wc -l < "${modified_files}" | tr -d ' '))
$(cat "${modified_files}")

## 删除文件 ($(wc -l < "${deleted_files}" | tr -d ' '))
$(cat "${deleted_files}")

## 总计变更文件 ($(wc -l < "${changed_files}" | tr -d ' '))
EOF
    
    log_success "文件对比完成"
    local added_count=$(wc -l < "${added_files}" | tr -d ' ')
    local modified_count=$(wc -l < "${modified_files}" | tr -d ' ')
    local deleted_count=$(wc -l < "${deleted_files}" | tr -d ' ')
    local total_count=$(wc -l < "${changed_files}" | tr -d ' ')
    
    log_info "新增文件: ${added_count}"
    log_info "修改文件: ${modified_count}"
    log_info "删除文件: ${deleted_count}"
    log_info "总计变更: ${total_count}"
    
    # 显示前几个变更的文件（调试信息）
    if [ "$total_count" -gt 0 ]; then
        log_info "前10个变更的文件："
        head -10 "${changed_files}" | while read -r file; do
            log_info "  - ${file}"
        done
    fi
    
    # 如果没有变更文件，显示警告
    if [ "$total_count" -eq 0 ]; then
        log_warning "未检测到任何文件变更"
        local current_file_count=0
        local base_file_count=0
        if [ -f "${current_list}" ]; then
            current_file_count=$(wc -l < "${current_list}" 2>/dev/null | tr -d ' ')
        fi
        if [ -f "${base_list}" ]; then
            base_file_count=$(wc -l < "${base_list}" 2>/dev/null | tr -d ' ')
        fi
        log_warning "当前版本 cvm补丁包文件清单 中的文件数: ${current_file_count}"
        log_warning "基准版本 cvm补丁包文件清单 中的文件数: ${base_file_count}"
        if [ "$current_file_count" -eq 0 ] || [ "$base_file_count" -eq 0 ]; then
            log_warning "提示：如果文件数为0，可能是manifest格式不正确，或者cvm补丁包文件清单为空"
        fi
    fi
    
    echo "${changed_files}"
}

# 创建全量包
create_all_package() {
    log_info "创建全量包..."
    
    local release_name="nfshm-xframe-${GAME_DIR}-${VERSION}-all-${TIMESTAMP}"
    local release_dir="${OUTPUT_DIR}/${release_name}"  # 第一个目录：存放manifest.md和.sha256
    local package_content_dir="${release_dir}/${release_name}"  # 第二个目录：存放所有打包文件
    
    # 创建发布目录（两层结构）
    if [ -d "${release_dir}" ]; then
        rm -rf "${release_dir}"
    fi
    mkdir -p "${package_content_dir}"
    
    # 复制所有文件到第二个目录
    log_info "复制文件到发布目录..."
    cp -r "${INSTALL_DIR}"/* "${package_content_dir}/"
    
    # 生成文件清单（在第一个目录下，与.sha256并列）
    generate_manifest "${release_dir}"
    
    # 全量包的 all_manifest.md 与 manifest.md 相同
    if [ -f "${release_dir}/manifest.md" ]; then
        log_info "创建 all_manifest.md（全量包与 manifest.md 相同）..."
        cp "${release_dir}/manifest.md" "${release_dir}/all_manifest.md"
        log_success "all_manifest.md 已创建: ${release_dir}/all_manifest.md"
    else
        log_error "manifest.md 不存在，无法创建 all_manifest.md"
        return 1
    fi
    
    # 生成版本信息文件（在第二个目录下）
    generate_version_file "${package_content_dir}"
    
    # 生成安装脚本（在第二个目录下）
    generate_install_script "${package_content_dir}"
    
    # 打包（打包第二个目录，打包文件放在第一个目录下）
    log_info "打包 ${release_name}.tar.gz..."
    local package_tgz="${release_dir}/${release_name}.tar.gz"
    cd "${release_dir}"
    tar -czf "${release_name}.tar.gz" "${release_name}"
    
    # 计算校验和（放在第一个目录下，当前目录已经是release_dir，使用相对路径）
    local sha256_file="${release_name}.tar.gz.sha256"
    if command -v sha256sum &> /dev/null; then
        sha256sum "${release_name}.tar.gz" > "${sha256_file}"
    elif command -v shasum &> /dev/null; then
        shasum -a 256 "${release_name}.tar.gz" > "${sha256_file}"
    fi
    
    # 更新 manifest.md 中的包MD5（当前目录已经是release_dir，使用相对路径）
    # 注意：当前目录已经是release_dir，所以使用相对路径
    local package_md5=$(get_file_md5 "${release_name}.tar.gz")
    if [ -z "$package_md5" ] || [ "$package_md5" = "unknown" ]; then
        log_error "无法获取包的MD5值"
        return
    fi
    
    if [ -f "manifest.md" ]; then
        log_info "更新 manifest.md 中的包MD5: ${package_md5}"
        # 更新包MD5（替换"待打包后计算"或旧的MD5值）
        # 使用临时文件方式，兼容Windows和Linux
        local temp_manifest=$(mktemp /tmp/manifest-update-$$.XXXXXX)
        if command -v sed &> /dev/null; then
            # 使用临时文件方式更新（更可靠）
            # 匹配格式：| 包MD5 | `待打包后计算` | 或 | 包MD5 | `xxx` |
            # 注意：在bash字符串中，反引号需要转义
            # 先更新包MD5
            sed "s/| 包MD5 | \`[^\`]*\` |/| 包MD5 | \`${package_md5}\` |/g" "manifest.md" > "${temp_manifest}"
            # 再更新cvm补丁包信息中的MD5（在同一行）
            sed "s/| MD5 | \`[^\`]*\` |/| MD5 | \`${package_md5}\` |/g" "${temp_manifest}" > "${temp_manifest}.tmp"
            mv "${temp_manifest}.tmp" "${temp_manifest}"
            mv "${temp_manifest}" "manifest.md"
            log_info "manifest.md 已更新，包MD5: ${package_md5}"
            
            # 验证更新是否成功
            if grep -q "| 包MD5 | \`${package_md5}\` |" "manifest.md" 2>/dev/null; then
                log_success "包MD5更新成功"
            else
                log_warning "包MD5更新可能失败，请检查manifest.md"
            fi
        fi
    else
        log_warning "manifest.md 不存在，无法更新包MD5"
    fi
    
    log_success "全量包已创建: ${package_tgz}"
    echo "${package_tgz}"
}

# 创建增量包
create_patch_package() {
    log_info "创建增量包..."
    
    local changed_files="$1"
    local release_name="nfshm-xframe-${GAME_DIR}-${VERSION}-patch-from-${BASE_VERSION}-${TIMESTAMP}"
    local release_dir="${OUTPUT_DIR}/${release_name}"  # 第一个目录：存放manifest.md和.sha256
    local package_content_dir="${release_dir}/${release_name}"  # 第二个目录：存放所有打包文件
    
    # 检查是否有变更文件
    if [ ! -s "${changed_files}" ]; then
        log_warning "没有变更的文件，跳过增量包创建"
        log_warning "可能的原因："
        log_warning "  1. 基准版本 ${BASE_VERSION} 的 all_manifest.md 可能不存在，请先打包基准版本的全量包或增量包"
        log_warning "  2. 源码修改后没有重新编译，或编译使用了缓存导致 bin/so 文件未更新"
        log_warning "  3. 源码修改没有影响二进制文件（如只修改了注释）"
        log_warning "  4. 当前版本和基准版本的文件 MD5 完全相同"
        return
    fi
    
    # 创建发布目录（两层结构）
    if [ -d "${release_dir}" ]; then
        rm -rf "${release_dir}"
    fi
    mkdir -p "${package_content_dir}"
    
    # 复制变更的文件到第二个目录（保持目录结构）
    log_info "复制变更的文件..."
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        
        # 优先从 PackageTemp 目录复制，如果不存在则从 Install 目录复制
        local src_file="${INSTALL_DIR}/${file}"
        local dest_file="${package_content_dir}/${file}"
        
            # 如果 PackageTemp 中不存在，尝试从 Install 目录复制
            if [ ! -f "${src_file}" ]; then
                local install_file="Install/${file}"
                if [ -f "${install_file}" ]; then
                    src_file="${install_file}"
                else
                    log_warning "  文件不存在，跳过: ${file} (尝试路径: ${INSTALL_DIR}/${file}, Install/${file})"
                    continue
                fi
            fi
            
            if [ -f "${src_file}" ]; then
                # 创建目标目录
                mkdir -p "$(dirname "${dest_file}")"
                # 复制文件
                cp "${src_file}" "${dest_file}"
            else
                log_warning "  文件不存在，跳过: ${file}"
            fi
    done < "${changed_files}"
    
    # 生成增量包的文件清单（只包含变更的文件，在第一个目录下，与.sha256并列）
    generate_patch_manifest "${release_dir}" "${changed_files}"
    
    # 生成版本信息文件（在第二个目录下）
    generate_version_file "${package_content_dir}"
    
    # 生成增量包说明文件（在第二个目录下）
    generate_patch_readme "${package_content_dir}" "${changed_files}"
    
    # 生成更新脚本（在第二个目录下）
    generate_update_script "${package_content_dir}" "${changed_files}"
    
    # 生成热更服务器脚本（在第二个目录下）
    generate_hot_reload_script "${package_content_dir}" "${changed_files}"
    
    # 打包（打包第二个目录，打包文件放在第一个目录下）
    log_info "打包 ${release_name}.tar.gz..."
    local package_tgz="${release_dir}/${release_name}.tar.gz"
    cd "${release_dir}"
    tar -czf "${release_name}.tar.gz" "${release_name}"
    
    # 计算校验和（放在第一个目录下，当前目录已经是release_dir，使用相对路径）
    local sha256_file="${release_name}.tar.gz.sha256"
    if command -v sha256sum &> /dev/null; then
        sha256sum "${release_name}.tar.gz" > "${sha256_file}"
    elif command -v shasum &> /dev/null; then
        shasum -a 256 "${release_name}.tar.gz" > "${sha256_file}"
    fi
    
    # 更新 manifest.md 中的包MD5（当前目录已经是release_dir，使用相对路径）
    # 注意：当前目录已经是release_dir，所以使用相对路径
    local package_md5=$(get_file_md5 "${release_name}.tar.gz")
    if [ -z "$package_md5" ] || [ "$package_md5" = "unknown" ]; then
        log_error "无法获取包的MD5值"
        return
    fi
    
    if [ -f "manifest.md" ]; then
        log_info "更新 manifest.md 中的包MD5: ${package_md5}"
        # 更新包MD5（替换"待打包后计算"或旧的MD5值）
        # 使用临时文件方式，兼容Windows和Linux
        local temp_manifest=$(mktemp /tmp/manifest-update-$$.XXXXXX)
        if command -v sed &> /dev/null; then
            # 使用临时文件方式更新（更可靠）
            # 匹配格式：| 包MD5 | `待打包后计算` | 或 | 包MD5 | `xxx` |
            # 注意：在bash字符串中，反引号需要转义
            # 先更新包MD5
            sed "s/| 包MD5 | \`[^\`]*\` |/| 包MD5 | \`${package_md5}\` |/g" "manifest.md" > "${temp_manifest}"
            # 再更新cvm补丁包信息中的MD5（在同一行）
            sed "s/| MD5 | \`[^\`]*\` |/| MD5 | \`${package_md5}\` |/g" "${temp_manifest}" > "${temp_manifest}.tmp"
            mv "${temp_manifest}.tmp" "${temp_manifest}"
            mv "${temp_manifest}" "manifest.md"
            log_info "manifest.md 已更新，包MD5: ${package_md5}"
            
            # 验证更新是否成功
            if grep -q "| 包MD5 | \`${package_md5}\` |" "manifest.md" 2>/dev/null; then
                log_success "包MD5更新成功"
            else
                log_warning "包MD5更新可能失败，请检查manifest.md"
            fi
        fi
    else
        log_warning "manifest.md 不存在，无法更新包MD5"
    fi
    
    # 生成 all_manifest.md（基于 --from-commit 创建全量清单）
    if [ -n "$FROM_COMMIT" ]; then
        log_info "基于 --from-commit ${FROM_COMMIT} 生成 all_manifest.md（全量清单）..."
        generate_all_manifest_for_patch "${release_dir}" "${FROM_COMMIT}"
    else
        log_warning "未指定 --from-commit，无法生成 all_manifest.md"
        log_warning "patch 包的 all_manifest.md 需要基于 --from-commit 创建全量清单"
    fi
    
    log_success "增量包已创建: ${package_tgz}"
    echo "${package_tgz}"
}

# 为 patch 包生成 all_manifest.md（包含从 from-commit 到 HEAD 的所有修改文件 + Bin 目录所有文件）
# 参数: $1 - 发布目录（release_dir）
#       $2 - FROM_COMMIT（起始提交）
generate_all_manifest_for_patch() {
    local release_dir="$1"
    local from_commit="$2"
    
    if [ -z "$release_dir" ] || [ -z "$from_commit" ]; then
        log_error "generate_all_manifest_for_patch: 需要指定发布目录和 FROM_COMMIT"
        return 1
    fi
    
    log_info "基于 --from-commit ${from_commit} 生成 all_manifest.md（包含 git log 修改文件 + Bin 目录所有文件）..."
    
    # 检查 INSTALL_DIR 是否存在
    if [ ! -d "${INSTALL_DIR}" ]; then
        log_error "INSTALL_DIR 不存在: ${INSTALL_DIR}"
        return 1
    fi
    
    local manifest_file="${release_dir}/all_manifest.md"
    
    # 获取Git分支信息
    local git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
    local current_commit=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
    
    # 获取git log信息
    local git_log_info=$(git log --pretty=format:"- %h - %an - %s" "${from_commit}^..HEAD" 2>/dev/null || \
                          git log --pretty=format:"- %h - %an - %s" --root "${from_commit}..HEAD" 2>/dev/null || echo "*无更新记录*")
    
    # 写入 all_manifest.md 头部
    cat > "${manifest_file}" <<EOF
# NFShmXFrame 文件清单

## 【职能化发布参数】

| 项目 | 值 |
|------|-----|
| 发布模版 | 【正式环境】全量更新 |
| 包名 | \`nfshm-xframe-${GAME_DIR}-${VERSION}-patch-from-${BASE_VERSION}-${TIMESTAMP}.tar.gz\` |
| 包MD5 | \`待打包后计算\` |
| 版本分支 | \`${git_branch}\` |

## 发布版本 & 备注

| 项目 | 值 |
|------|-----|
| 发布版本 | ${VERSION} |
| 备注 | - |

## 发布环境

| 项目 | 值 |
|------|-----|
| 环境 | 正式环境 |

## cvm补丁包信息

| 项目 | 值 |
|------|-----|
| 包名 | \`nfshm-xframe-${GAME_DIR}-${VERSION}-patch-from-${BASE_VERSION}-${TIMESTAMP}.tar.gz\` |
| MD5 | \`待打包后计算\` |

## 服务器更新清单

${git_log_info}

EOF
    
    # 创建临时文件用于分类
    local update_files_list=$(mktemp /tmp/update-files-list-all-$$.XXXXXX)
    local source_changed_list=$(mktemp /tmp/source-changed-list-all-$$.XXXXXX)
    local cvm_package_list=$(mktemp /tmp/cvm-package-list-all-$$.XXXXXX)
    
    # 1. 获取 git log 中修改的文件（从 from_commit 到 HEAD）
    log_info "获取从 ${from_commit} 到 HEAD 的 git log 修改文件..."
    local git_changed_files=$(mktemp /tmp/git-changed-files-all-$$.XXXXXX)
    if git diff --name-only --diff-filter=ACMR "${from_commit}^..HEAD" > "$git_changed_files" 2>/dev/null; then
        : # 成功
    else
        # 可能是第一个提交，尝试使用 --root
        git diff --name-only --diff-filter=ACMR --root "${from_commit}..HEAD" > "$git_changed_files" 2>/dev/null || true
    fi
    
    local git_file_count=0
    if [ -s "$git_changed_files" ]; then
        git_file_count=$(wc -l < "$git_changed_files" | tr -d ' ')
        log_info "  找到 ${git_file_count} 个 git 修改文件"
        
        # 处理 git log 中修改的所有文件
        while IFS= read -r git_file; do
            [ -z "$git_file" ] && continue
            
            # 处理打包文件（Install/ 目录下的文件）
            if [[ "$git_file" == Install/Bin/* ]] || \
               [[ "$git_file" == Install/Config/* ]] || \
               [[ "$git_file" == Install/Plugin/* ]] || \
               [[ "$git_file" == Install/LuaScript/* ]]; then
                local rel_path="${git_file#Install/}"
                local actual_file=""
                
                # 尝试多个路径查找打包文件
                if [ -f "${INSTALL_DIR}/${rel_path}" ]; then
                    actual_file="${INSTALL_DIR}/${rel_path}"
                elif [ -f "${PROJECT_ROOT}/${git_file}" ]; then
                    actual_file="${PROJECT_ROOT}/${git_file}"
                elif [ -f "$git_file" ]; then
                    actual_file="$git_file"
                fi
                
                if [ -n "$actual_file" ] && [ -f "$actual_file" ]; then
                    local md5_value=$(get_file_md5 "$actual_file")
                    if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                        log_warning "无法获取打包文件 MD5: ${git_file}"
                        md5_value="unknown"
                    fi
                    # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                    local git_info=$(get_git_info "$git_file")
                    local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                    local author=$(echo "$git_info" | cut -d' ' -f2-)
                    
                    # 添加到cvm补丁包文件清单（所有打包文件）
                    if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                        echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
                    fi
                    
                    # 添加到更新文件清单（排除 LuaScript/ 下的文件，这些是代码）
                    if [[ "$rel_path" != LuaScript/* ]] && [[ "$rel_path" != */LuaScript/* ]]; then
                        if ! grep -q "^${rel_path} " "$update_files_list" 2>/dev/null; then
                            echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
                        fi
                    fi
                else
                    log_warning "打包文件不存在，跳过: ${git_file}"
                fi
            fi
            
            # 处理源码文件（*.h, *.hpp, *.cpp, *.c, *.cc 等，排除 Install/Plugin/ 下的 lua 文件）
            if is_source_file "$git_file" && [[ "$git_file" != Install/Plugin/* ]] && [[ "$git_file" != */Plugin/* ]]; then
                # 尝试获取文件的 MD5（如果文件存在）
                local md5_value="unknown"
                local source_file_path=""
                
                # 尝试多个路径查找源码文件
                if [ -f "$git_file" ]; then
                    source_file_path="$git_file"
                elif [ -f "${PROJECT_ROOT}/${git_file}" ]; then
                    source_file_path="${PROJECT_ROOT}/${git_file}"
                fi
                
                if [ -n "$source_file_path" ] && [ -f "$source_file_path" ]; then
                    md5_value=$(get_file_md5 "$source_file_path")
                    if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                        log_warning "无法获取源码文件 MD5: ${git_file}"
                    fi
                else
                    log_warning "源码文件不存在，MD5 标记为 unknown: ${git_file}"
                fi
                
                # 优化：使用 get_git_info 函数一次性获取 commit 和 author
                local git_info=$(get_git_info "$git_file")
                local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
                local author=$(echo "$git_info" | cut -d' ' -f2-)
                
                if ! grep -q "^${git_file} " "$source_changed_list" 2>/dev/null; then
                    echo "${git_file} ${md5_value} ${commit_hash} ${author}" >> "$source_changed_list"
                fi
            fi
        done < "$git_changed_files"
    fi
    rm -f "$git_changed_files"
    
    # 2. 扫描 Bin 目录的所有二进制文件（确保包含所有二进制文件）
    if [ -d "${INSTALL_DIR}/Bin" ]; then
        log_info "扫描 Bin 目录: ${INSTALL_DIR}/Bin"
        local bin_file_count=0
        while IFS= read -r file; do
            [ -z "$file" ] && continue
            [ ! -f "$file" ] && continue
            local rel_path="${file#${INSTALL_DIR}/}"
            local md5_value=$(get_file_md5 "$file")
            if [ -z "$md5_value" ] || [ "$md5_value" = "unknown" ]; then
                log_warning "无法获取文件 MD5，跳过: ${file}"
                continue
            fi
            local git_path="Install/${rel_path}"
            # 优化：使用 get_git_info 函数一次性获取 commit 和 author
            local git_info=$(get_git_info "$git_path")
            local commit_hash=$(echo "$git_info" | cut -d' ' -f1)
            local author=$(echo "$git_info" | cut -d' ' -f2-)
            
            # 添加到更新文件清单和cvm补丁包文件清单（避免重复）
            if ! grep -q "^${rel_path} " "$update_files_list" 2>/dev/null; then
                echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$update_files_list"
            fi
            if ! grep -q "^${rel_path} " "$cvm_package_list" 2>/dev/null; then
                echo "${rel_path} ${md5_value} ${commit_hash} ${author}" >> "$cvm_package_list"
            fi
            bin_file_count=$((bin_file_count + 1))
        done < <(find "${INSTALL_DIR}/Bin" -type f 2>/dev/null | sort)
        log_info "  Bin 目录: 找到 ${bin_file_count} 个文件"
    else
        log_warning "Bin 目录不存在: ${INSTALL_DIR}/Bin"
    fi
    
    # 写入更新文件清单
    echo "" >> "${manifest_file}"
    echo "## 更新文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$update_files_list" ]; then
        local update_count=$(wc -l < "$update_files_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$update_files_list"
        log_info "更新文件: ${update_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
    fi
    
    # 写入源码改动清单
    echo "" >> "${manifest_file}"
    echo "## 源码改动清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$source_changed_list" ]; then
        local source_count=$(wc -l < "$source_changed_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            local commit=$(echo "$line" | awk '{print $3}')
            local author=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
            if [ "$commit" = "unknown" ]; then
                commit="~"
            fi
            echo "| \`${file_path}\` | \`${md5_val}\` | \`${commit}\` | ${author} |" >> "${manifest_file}"
        done < "$source_changed_list"
        log_info "源码文件: ${source_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无源码文件*" >> "${manifest_file}"
    fi
    
    # 写入cvm补丁包文件清单
    echo "" >> "${manifest_file}"
    echo "## cvm补丁包文件清单" >> "${manifest_file}"
    echo "" >> "${manifest_file}"
    if [ -s "$cvm_package_list" ]; then
        local cvm_count=$(wc -l < "$cvm_package_list" | tr -d ' ')
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            local file_path=$(echo "$line" | awk '{print $1}')
            local md5_val=$(echo "$line" | awk '{print $2}')
            echo "| \`${file_path}\` | \`${md5_val}\` |  |  |" >> "${manifest_file}"
        done < "$cvm_package_list"
        log_info "cvm补丁包文件: ${cvm_count} 个"
    else
        echo "| 文件 | MD5 | Git 版本号 | 最后修改 |" >> "${manifest_file}"
        echo "|------|-----|-----------|---------|" >> "${manifest_file}"
        echo "*无文件*" >> "${manifest_file}"
    fi
    
    # 清理临时文件
    rm -f "$update_files_list" "$cvm_package_list"
    
    log_success "all_manifest.md 已生成: ${manifest_file}"
}

# 更新 all_manifest.md（从基准版本复制并应用变更）
# 参数: $1 - 发布目录（release_dir）
#       $2 - 变更文件列表文件路径
#       $3 - 当前版本的清单文件路径（manifest.md）
update_all_manifest() {
    local release_dir="$1"
    local changed_files="$2"
    local current_manifest="$3"
    
    log_info "更新 all_manifest.md..."
    
    # 查找基准版本的 all_manifest.md
    local base_manifest=$(find_base_manifest "${BASE_VERSION}")
    
    if [ -z "$base_manifest" ] || [ ! -f "$base_manifest" ]; then
        log_error "无法找到基准版本 ${BASE_VERSION} 的 all_manifest.md"
        log_error "请先打包基准版本的全量包或增量包"
        return 1
    fi
    
    log_info "从基准版本复制 all_manifest.md: ${base_manifest}"
    cp "$base_manifest" "${release_dir}/all_manifest.md"
    
    # 读取变更文件列表
    local added_files="${OUTPUT_DIR}/added-files-${VERSION}.txt"
    local modified_files="${OUTPUT_DIR}/modified-files-${VERSION}.txt"
    local deleted_files="${OUTPUT_DIR}/deleted-files-${VERSION}.txt"
    
    # 创建临时文件用于更新
    local temp_manifest=$(mktemp /tmp/all-manifest-update-$$.XXXXXX)
    cp "${release_dir}/all_manifest.md" "$temp_manifest"
    
    # 验证当前版本的清单文件是否存在
    if [ -z "$current_manifest" ] || [ ! -f "$current_manifest" ]; then
        log_error "当前版本的清单文件不存在: ${current_manifest}"
        rm -f "$temp_manifest"
        return 1
    fi
    
    # 1. 删除已删除的文件
    if [ -f "$deleted_files" ] && [ -s "$deleted_files" ]; then
        log_info "从 all_manifest.md 中删除已删除的文件..."
        while IFS= read -r file; do
            [ -z "$file" ] && continue
            # 从 cvm补丁包文件清单表格中删除该文件的行
            awk -v file="$file" '
                /^## cvm补丁包文件清单/ { in_section=1; print; next }
                /^## / { in_section=0 }
                in_section && $0 ~ "\\| *`" file "`" {
                    # 跳过这一行（删除）
                    next
                }
                { print }
            ' "$temp_manifest" > "${temp_manifest}.tmp"
            mv "${temp_manifest}.tmp" "$temp_manifest"
        done < "$deleted_files"
    fi
    
    # 2. 更新已修改的文件（替换 MD5）
    if [ -f "$modified_files" ] && [ -s "$modified_files" ]; then
        log_info "更新 all_manifest.md 中已修改文件的 MD5..."
        while IFS= read -r file; do
            [ -z "$file" ] && continue
            
            # 从当前版本的清单中提取该文件的新 MD5
            local new_md5=$(awk -v file="$file" '
                /^## cvm补丁包文件清单/ { in_section=1; next }
                /^## / { in_section=0 }
                in_section && $0 ~ "\\| *`" file "`" {
                    match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)
                    if (arr[1] != "") print arr[1]
                    exit
                }
            ' "$current_manifest" 2>/dev/null)
            
            if [ -n "$new_md5" ]; then
                # 替换该文件的 MD5
                awk -v file="$file" -v new_md5="$new_md5" '
                    /^## cvm补丁包文件清单/ { in_section=1; print; next }
                    /^## / { in_section=0 }
                    in_section && $0 ~ "\\| *`" file "`" {
                        # 替换 MD5（第二个反引号内的内容）
                        match($0, /(\| *`[^`]+` *\| *`)([^`]+)(`.*)/, arr)
                        if (arr[1] != "" && arr[3] != "") {
                            print arr[1] new_md5 arr[3]
                        } else {
                            print $0
                        }
                        next
                    }
                    { print }
                ' "$temp_manifest" > "${temp_manifest}.tmp"
                mv "${temp_manifest}.tmp" "$temp_manifest"
            fi
        done < "$modified_files"
    fi
    
    # 3. 添加新增的文件
    if [ -f "$added_files" ] && [ -s "$added_files" ]; then
        log_info "向 all_manifest.md 中添加新增的文件..."
        while IFS= read -r file; do
            [ -z "$file" ] && continue
            
            # 从当前版本的清单中提取该文件的 MD5
            local new_md5=$(awk -v file="$file" '
                /^## cvm补丁包文件清单/ { in_section=1; next }
                /^## / { in_section=0 }
                in_section && $0 ~ "\\| *`" file "`" {
                    match($0, /\| *`[^`]+` *\| *`([^`]+)`/, arr)
                    if (arr[1] != "") print arr[1]
                    exit
                }
            ' "$current_manifest" 2>/dev/null)
            
            if [ -n "$new_md5" ]; then
                # 检查文件是否已经在 all_manifest.md 中（避免重复添加）
                if ! grep -q "| \`${file}\` |" "$temp_manifest" 2>/dev/null; then
                    # 在 cvm补丁包文件清单表格的最后一行（表格结束前）添加新行
                    awk -v file="$file" -v new_md5="$new_md5" '
                        /^## cvm补丁包文件清单/ { 
                            in_section=1
                            print
                            getline  # 跳过空行
                            print
                            getline  # 跳过表头
                            print
                            getline  # 跳过分隔线
                            print
                            next
                        }
                        /^## / && in_section {
                            # 遇到下一个章节，在之前插入新行
                            print "| \`" file "\` | \`" new_md5 "\` |  |  |"
                            in_section=0
                            print
                            next
                        }
                        { print }
                    ' "$temp_manifest" > "${temp_manifest}.tmp"
                    mv "${temp_manifest}.tmp" "$temp_manifest"
                    log_info "  添加新文件到 all_manifest.md: ${file}"
                else
                    log_info "  文件已存在于 all_manifest.md，跳过: ${file}"
                fi
            else
                log_warning "  无法获取文件 ${file} 的 MD5，跳过添加到 all_manifest.md"
            fi
        done < "$added_files"
    fi
    
    # 更新版本信息（更新发布版本号和时间戳）
    if [ -f "$temp_manifest" ]; then
        # 更新发布版本号
        sed "s/| 发布版本 | ${BASE_VERSION} |/| 发布版本 | ${VERSION} |/g" "$temp_manifest" > "${temp_manifest}.tmp"
        mv "${temp_manifest}.tmp" "$temp_manifest"
        
        # 更新生成时间（如果存在）
        local current_time=$(date '+%Y-%m-%d %H:%M:%S')
        sed "s/生成时间:.*/生成时间: ${current_time}/g" "$temp_manifest" > "${temp_manifest}.tmp" 2>/dev/null || cp "$temp_manifest" "${temp_manifest}.tmp"
        mv "${temp_manifest}.tmp" "$temp_manifest"
        
        # 替换为最终的 all_manifest.md
        mv "$temp_manifest" "${release_dir}/all_manifest.md"
        log_success "all_manifest.md 已更新（包含所有变更：新增、修改、删除）"
        log_info "  - 新增文件: $(wc -l < "$added_files" 2>/dev/null | tr -d ' ' || echo 0)"
        log_info "  - 修改文件: $(wc -l < "$modified_files" 2>/dev/null | tr -d ' ' || echo 0)"
        log_info "  - 删除文件: $(wc -l < "$deleted_files" 2>/dev/null | tr -d ' ' || echo 0)"
    else
        log_warning "更新 all_manifest.md 失败"
    fi
}

# 生成版本信息文件
generate_version_file() {
    local release_dir="$1"
    
    log_info "生成版本信息文件..."
    
    cat > "${release_dir}/VERSION.txt" <<EOF
Project: NFShmXFrame
Game: ${GAME_DIR}
Version: ${VERSION}
Build Type: ${BUILD_TYPE}
Package Type: ${PACKAGE_TYPE}
$(if [ "$PACKAGE_TYPE" = "patch" ]; then echo "Base Version: ${BASE_VERSION}"; fi)
Build Time: $(date '+%Y-%m-%d %H:%M:%S')
Build Host: $(hostname)
Git Commit: $(git rev-parse HEAD 2>/dev/null || echo "unknown")
Git Branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
EOF
    
    log_success "版本信息文件已生成"
}

# 生成安装脚本
generate_install_script() {
    local release_dir="$1"
    
    log_info "生成安装脚本..."
    
    cat > "${release_dir}/install.sh" <<INSTALL_EOF
#!/bin/bash
# ============================================================================
# NFShmXFrame 安装脚本
# ============================================================================

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_info() {
    echo -e "\${BLUE}[INFO]\${NC} \$1"
}

log_success() {
    echo -e "\${GREEN}[SUCCESS]\${NC} \$1"
}

log_error() {
    echo -e "\${RED}[ERROR]\${NC} \$1"
}

echo "=========================================="
echo "NFShmXFrame 安装脚本"
echo "=========================================="

# 默认安装目录：当前用户目录下的 nfshm-xframe-bin
INSTALL_DIR="\${HOME}/nfshm-xframe-bin"

# 询问安装目录
read -p "请输入安装目录 [\${INSTALL_DIR}]: " USER_DIR
INSTALL_DIR=\${USER_DIR:-\$INSTALL_DIR}

log_info "安装目录: \${INSTALL_DIR}"

# 检查是否需要 sudo（一般不需要，除非安装目录需要 root 权限而自己又不是 root）
SUDO=""
NEED_SUDO=false

# 检查安装目录的父目录是否存在且可写
INSTALL_PARENT=\$(dirname "\${INSTALL_DIR}")
if [ ! -w "\${INSTALL_PARENT}" ] 2>/dev/null; then
    # 父目录不可写，需要检查是否需要 sudo
    if [ "\$EUID" -ne 0 ]; then
        # 尝试创建目录测试权限
        if ! mkdir -p "\${INSTALL_DIR}" 2>/dev/null; then
            NEED_SUDO=true
            SUDO="sudo"
            log_info "安装目录需要 root 权限，将使用 sudo"
        else
            # 可以创建，删除测试目录
            rmdir "\${INSTALL_DIR}" 2>/dev/null || true
        fi
    fi
else
    # 父目录可写，不需要 sudo
    log_info "安装目录在用户目录下，无需 root 权限"
fi

# 如果安装目录已存在，先备份整个目录（包括日志）
if [ -d "\${INSTALL_DIR}" ]; then
    log_info "检测到安装目录已存在，准备备份整个目录..."
    
    # 尝试读取现有版本的版本号
    old_version="unknown"
    if [ -f "\${INSTALL_DIR}/VERSION.txt" ]; then
        # 从 VERSION.txt 中提取版本号
        old_version=\$(grep "^Version:" "\${INSTALL_DIR}/VERSION.txt" 2>/dev/null | cut -d' ' -f2 | tr -d '[:space:]' || echo "unknown")
        if [ -z "\$old_version" ] || [ "\$old_version" = "" ]; then
            old_version="unknown"
        fi
    fi
    
    # 创建备份目录
    # 如果 INSTALL_DIR 以 ~ 开头，备份目录也放在 ~ 下；否则放在 INSTALL_DIR 的父目录下
    if [[ "\${INSTALL_DIR}" == ~* ]]; then
        BACKUP_DIR="\${HOME}/Backup"
    else
        INSTALL_PARENT=\$(dirname "\${INSTALL_DIR}")
        BACKUP_DIR="\${INSTALL_PARENT}/Backup"
    fi
    \$SUDO mkdir -p "\${BACKUP_DIR}"
    
    # 生成备份目录名：nfshm-xframe-bin-backup-<版本号>-<时间戳>
    backup_timestamp=\$(date '+%Y%m%d_%H%M%S')
    backup_dir="\${BACKUP_DIR}/nfshm-xframe-bin-backup-\${old_version}-\${backup_timestamp}"
    
    # 备份整个目录（包括日志）
    log_info "备份原安装目录到: \${backup_dir}（包含所有文件，包括日志）..."
    if [ "\$SUDO" = "sudo" ]; then
        # 使用 mv 移动整个目录（最快的方式）
        sudo mv "\${INSTALL_DIR}" "\${backup_dir}" 2>/dev/null || {
            # mv 失败，使用 cp + rm 方式
            sudo cp -r "\${INSTALL_DIR}" "\${backup_dir}" 2>/dev/null || true
            sudo rm -rf "\${INSTALL_DIR}" 2>/dev/null || true
        }
    else
        # 使用 mv 移动整个目录（最快的方式）
        mv "\${INSTALL_DIR}" "\${backup_dir}" 2>/dev/null || {
            # mv 失败，使用 cp + rm 方式
            cp -r "\${INSTALL_DIR}" "\${backup_dir}" 2>/dev/null || true
            rm -rf "\${INSTALL_DIR}" 2>/dev/null || true
        }
    fi
    
    log_success "备份完成: \${backup_dir}"
fi

# 创建安装目录
log_info "创建安装目录..."
\$SUDO mkdir -p "\${INSTALL_DIR}"

# 安全复制文件（支持热更新，即使文件正在运行也能替换）
# 使用原子替换方式：先复制到临时文件，然后重命名
safe_copy_file() {
    local src_file="\$1"
    local dest_file="\$2"
    local use_sudo="\$3"
    
    if [ ! -f "\$src_file" ]; then
        return 1
    fi
    
    # 创建目标目录
    local dest_dir=\$(dirname "\$dest_file")
    if [ "\$use_sudo" = "sudo" ]; then
        sudo mkdir -p "\$dest_dir"
    else
        mkdir -p "\$dest_dir"
    fi
    
    # 使用原子替换方式：先复制到临时文件，然后重命名
    # 这样可以避免"文本文件忙"错误，支持热更新
    # Linux 允许删除正在运行的文件（只要文件描述符还打开着），所以先删除再移动是安全的
    local temp_file="\${dest_file}.tmp.\$\$"
    
    # 复制到临时文件
    if [ "\$use_sudo" = "sudo" ]; then
        sudo cp --preserve=timestamps "\$src_file" "\$temp_file" || return 1
        # 原子替换策略：
        # 1. 先尝试直接 mv（如果文件不在运行，这是最快的）
        # 2. 如果失败，先删除旧文件（Linux 允许删除正在运行的文件），再移动新文件
        if ! sudo mv "\$temp_file" "\$dest_file" 2>/dev/null; then
            # mv 失败，说明目标文件可能正在运行，先删除再移动
            sudo rm -f "\$dest_file" 2>/dev/null || true
            if ! sudo mv "\$temp_file" "\$dest_file"; then
                sudo rm -f "\$temp_file" 2>/dev/null || true
                return 1
            fi
        fi
    else
        cp --preserve=timestamps "\$src_file" "\$temp_file" || return 1
        # 原子替换策略：
        # 1. 先尝试直接 mv（如果文件不在运行，这是最快的）
        # 2. 如果失败，先删除旧文件（Linux 允许删除正在运行的文件），再移动新文件
        if ! mv "\$temp_file" "\$dest_file" 2>/dev/null; then
            # mv 失败，说明目标文件可能正在运行，先删除再移动
            rm -f "\$dest_file" 2>/dev/null || true
            if ! mv "\$temp_file" "\$dest_file"; then
                rm -f "\$temp_file" 2>/dev/null || true
                return 1
            fi
        fi
    fi
    
    return 0
}

# 判断文件是否需要安全复制（可执行文件和 .so 文件需要安全复制）
needs_safe_copy() {
    local file="\$1"
    local filename=\$(basename "\$file")
    
    # 检查是否是 .so 文件
    if [[ "\$filename" == *.so ]] || [[ "\$filename" == *.so.* ]]; then
        return 0
    fi
    
    # 检查是否是可执行文件
    if [ -x "\$file" ]; then
        return 0
    fi
    
    return 1
}

# 安全复制目录（递归复制，对 .so 和可执行文件使用安全复制）
safe_copy_directory() {
    local src_dir="\$1"
    local dest_dir="\$2"
    local use_sudo="\$3"
    
    if [ ! -d "\$src_dir" ]; then
        return 1
    fi
    
    # 创建目标目录
    if [ "\$use_sudo" = "sudo" ]; then
        sudo mkdir -p "\$dest_dir"
    else
        mkdir -p "\$dest_dir"
    fi
    
    # 使用 for 循环遍历，避免子 shell 问题
    for item in "\${src_dir}"/*; do
        # 处理通配符未匹配的情况
        [ -e "\$item" ] || continue
        
        local item_name=\$(basename "\$item")
        local dest_item="\${dest_dir}/\${item_name}"
        
        if [ -d "\$item" ]; then
            # 递归复制子目录
            if ! safe_copy_directory "\$item" "\$dest_item" "\$use_sudo"; then
                return 1
            fi
        elif [ -f "\$item" ]; then
            # 判断是否需要安全复制
            if needs_safe_copy "\$item"; then
                # 使用安全复制方式（支持热更新）
                if ! safe_copy_file "\$item" "\$dest_item" "\$use_sudo"; then
                    log_error "复制文件失败: \$item -> \$dest_item"
                    return 1
                fi
            else
                # 普通文件直接复制
                if [ "\$use_sudo" = "sudo" ]; then
                    if ! sudo cp --preserve=timestamps "\$item" "\$dest_item"; then
                        return 1
                    fi
                else
                    if ! cp --preserve=timestamps "\$item" "\$dest_item"; then
                        return 1
                    fi
                fi
            fi
        fi
    done
    
    return 0
}

# 复制文件（支持热更新）
log_info "开始复制文件到安装目录..."
echo ""

# 复制 Config 目录
if [ -d "Config" ]; then
    log_info "复制 Config/ 目录..."
\$SUDO cp -r --preserve=timestamps Config "\${INSTALL_DIR}/" 2>/dev/null || true
fi

# 复制 LuaScript 目录
if [ -d "LuaScript" ]; then
    log_info "复制 LuaScript/ 目录..."
\$SUDO cp -r --preserve=timestamps LuaScript "\${INSTALL_DIR}/" 2>/dev/null || true
fi

# 复制 Plugin 目录
if [ -d "Plugin" ]; then
    log_info "复制 Plugin/ 目录..."
    if ! safe_copy_directory "Plugin" "\${INSTALL_DIR}/Plugin" "\$SUDO"; then
        log_error "复制 Plugin 目录失败"
        exit 1
    fi
fi

# 复制 Bin 目录（对 .so 和可执行文件使用安全复制，支持热更新）
if [ -d "Bin" ]; then
    log_info "复制 Bin/ 目录（.so 和可执行文件支持热更新）..."
    # 创建目标 Bin 目录
    if [ "\$SUDO" = "sudo" ]; then
        sudo mkdir -p "\${INSTALL_DIR}/Bin"
    else
        mkdir -p "\${INSTALL_DIR}/Bin"
    fi
    # 复制 Bin 目录中的文件，排除 logs 子目录
    for item in Bin/*; do
        [ -e "\$item" ] || continue
        item_name=\$(basename "\$item")
        # 跳过 logs 目录
        if [ "\$item_name" = "logs" ]; then
            continue
        fi
        dest_item="\${INSTALL_DIR}/Bin/\${item_name}"
        if [ -d "\$item" ]; then
            # 递归复制子目录（排除 logs）
            if ! safe_copy_directory "\$item" "\$dest_item" "\$SUDO"; then
                log_error "复制 Bin 子目录失败: \$item"
        exit 1
    fi
        elif [ -f "\$item" ]; then
            # 判断是否需要安全复制
            if needs_safe_copy "\$item"; then
                if ! safe_copy_file "\$item" "\$dest_item" "\$SUDO"; then
                    log_error "复制 Bin 文件失败: \$item"
                    exit 1
                fi
            else
                if [ "\$SUDO" = "sudo" ]; then
                    sudo cp --preserve=timestamps "\$item" "\$dest_item" || exit 1
                else
                    cp --preserve=timestamps "\$item" "\$dest_item" || exit 1
                fi
            fi
        fi
    done
fi

# 复制根目录下的可执行文件（使用原子替换方式，支持热更新）
log_info "复制可执行文件..."
for file in NFPluginLoader NFServerController; do
    if [ -f "\$file" ]; then
        log_info "  - \$file"
        dest_file="\${INSTALL_DIR}/\$file"
        # 使用安全复制方式（支持热更新）
        if ! safe_copy_file "\$file" "\$dest_file" "\$SUDO"; then
            log_error "复制文件失败: \$file -> \$dest_file"
            exit 1
        fi
    fi
done

# 复制配置文件
if ls *.conf >/dev/null 2>&1; then
    log_info "复制配置文件..."
    for file in *.conf; do
        [ -f "\$file" ] && log_info "  - \$file"
        \$SUDO cp --preserve=timestamps "\$file" "\${INSTALL_DIR}/" 2>/dev/null || true
    done
fi

# 复制文档和脚本文件
log_info "复制文档和脚本文件..."
\$SUDO cp --preserve=timestamps *.md "\${INSTALL_DIR}/" 2>/dev/null || true
\$SUDO cp --preserve=timestamps *.txt "\${INSTALL_DIR}/" 2>/dev/null || true
# 复制 .sh 文件，但排除 install.sh 和 update.sh
for sh_file in *.sh; do
    [ -f "\$sh_file" ] && [ "\$sh_file" != "install.sh" ] && [ "\$sh_file" != "update.sh" ] && \
    \$SUDO cp --preserve=timestamps "\$sh_file" "\${INSTALL_DIR}/" 2>/dev/null || true
done

# 设置可执行权限
log_info "设置可执行权限..."
\$SUDO chmod +x "\${INSTALL_DIR}/NFPluginLoader" 2>/dev/null || true
\$SUDO chmod +x "\${INSTALL_DIR}/NFServerController" 2>/dev/null || true

# 清理所有残留的临时文件（.tmp.*）
log_info "清理临时文件..."
if [ "\$SUDO" = "sudo" ]; then
    sudo find "\${INSTALL_DIR}" -type f -name "*.tmp.*" -delete 2>/dev/null || true
else
    find "\${INSTALL_DIR}" -type f -name "*.tmp.*" -delete 2>/dev/null || true
fi

echo "=========================================="
log_success "安装完成！"
echo "=========================================="
echo ""
echo "安装目录: \${INSTALL_DIR}"
echo ""
echo "启动命令:"
echo "  cd \${INSTALL_DIR}"
echo "  ./NFServerController"
echo ""
echo "=========================================="
INSTALL_EOF

    chmod +x "${release_dir}/install.sh"
    
    log_success "安装脚本已生成"
}

# 生成增量包说明文件
generate_patch_readme() {
    local release_dir="$1"
    local changed_files="$2"
    local release_name=$(basename "${release_dir}")
    
    log_info "生成增量包说明文件..."
    
    # 读取变更文件列表
    local added_count=0
    local modified_count=0
    local deleted_count=0
    
    if [ -f "${OUTPUT_DIR}/added-files-${VERSION}.txt" ]; then
        added_count=$(wc -l < "${OUTPUT_DIR}/added-files-${VERSION}.txt" | tr -d ' ')
    fi
    
    if [ -f "${OUTPUT_DIR}/modified-files-${VERSION}.txt" ]; then
        modified_count=$(wc -l < "${OUTPUT_DIR}/modified-files-${VERSION}.txt" | tr -d ' ')
    fi
    
    if [ -f "${OUTPUT_DIR}/deleted-files-${VERSION}.txt" ]; then
        deleted_count=$(wc -l < "${OUTPUT_DIR}/deleted-files-${VERSION}.txt" | tr -d ' ')
    fi
    
    cat > "${release_dir}/PATCH_README.md" <<EOF
# NFShmXFrame 增量更新包

## 版本信息

- **当前版本**: ${VERSION}
- **基准版本**: ${BASE_VERSION}
- **项目**: ${GAME_DIR}
- **构建类型**: ${BUILD_TYPE}

## 更新内容

本增量包包含以下变更：

### 新增文件 (${added_count})
$(if [ -f "${OUTPUT_DIR}/added-files-${VERSION}.txt" ] && [ "$added_count" -gt 0 ]; then
    cat "${OUTPUT_DIR}/added-files-${VERSION}.txt" | sed 's/^/- /'
else
    echo "无"
fi)

### 修改文件 (${modified_count})
$(if [ -f "${OUTPUT_DIR}/modified-files-${VERSION}.txt" ] && [ "$modified_count" -gt 0 ]; then
    cat "${OUTPUT_DIR}/modified-files-${VERSION}.txt" | sed 's/^/- /'
else
    echo "无"
fi)

### 删除文件 (${deleted_count})
$(if [ -f "${OUTPUT_DIR}/deleted-files-${VERSION}.txt" ] && [ "$deleted_count" -gt 0 ]; then
    cat "${OUTPUT_DIR}/deleted-files-${VERSION}.txt" | sed 's/^/- /'
else
    echo "无"
fi)

## 安装方法

1. 解压增量包：
   \`\`\`bash
   tar -xzf ${release_name}.tar.gz
   cd ${release_name}
   \`\`\`

2. 运行更新脚本：
   \`\`\`bash
   chmod +x update.sh
   ./update.sh
   \`\`\`

## 注意事项

- 请确保已安装基准版本 ${BASE_VERSION} 的全量包
- 更新前建议备份重要数据
- 更新后请重启服务器

EOF
    
    log_success "增量包说明文件已生成"
}

# 生成更新脚本（用于增量包）
generate_update_script() {
    local release_dir="$1"
    local changed_files="$2"
    
    log_info "生成更新脚本..."
    
    cat > "${release_dir}/update.sh" <<'UPDATE_EOF'
#!/bin/bash
# ============================================================================
# NFShmXFrame 增量更新脚本（支持热更新）
# ============================================================================

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

echo "=========================================="
echo "NFShmXFrame 增量更新脚本"
echo "=========================================="

# 默认安装目录：当前用户目录下的 nfshm-xframe-bin
INSTALL_DIR="${HOME}/nfshm-xframe-bin"

# 询问安装目录
read -p "请输入安装目录 [${INSTALL_DIR}]: " USER_DIR
INSTALL_DIR=${USER_DIR:-$INSTALL_DIR}

log_info "安装目录: ${INSTALL_DIR}"

# 检查安装目录是否存在
if [ ! -d "${INSTALL_DIR}" ]; then
    log_error "安装目录不存在: ${INSTALL_DIR}"
    log_error "请先安装基准版本的全量包"
    exit 1
fi

# 检查是否需要 sudo
SUDO=""
NEED_SUDO=false

# 检查安装目录是否可写
if [ ! -w "${INSTALL_DIR}" ] 2>/dev/null; then
    if [ "$EUID" -ne 0 ]; then
        NEED_SUDO=true
        SUDO="sudo"
        log_info "安装目录需要 root 权限，将使用 sudo"
    fi
else
    log_info "安装目录在用户目录下，无需 root 权限"
fi

# 更新前备份原安装目录（包含版本号）
log_info "更新前备份原安装目录..."

# 尝试读取现有版本的版本号
old_version="unknown"
if [ -f "${INSTALL_DIR}/VERSION.txt" ]; then
    # 从 VERSION.txt 中提取版本号
    old_version=$(grep "^Version:" "${INSTALL_DIR}/VERSION.txt" 2>/dev/null | cut -d' ' -f2 | tr -d '[:space:]' || echo "unknown")
    if [ -z "$old_version" ] || [ "$old_version" = "" ]; then
        old_version="unknown"
    fi
fi

# 创建备份目录
# 如果 INSTALL_DIR 以 ~ 开头，备份目录也放在 ~ 下；否则放在 INSTALL_DIR 的父目录下
if [[ "${INSTALL_DIR}" == ~* ]]; then
    BACKUP_DIR="${HOME}/Backup"
else
    INSTALL_PARENT=$(dirname "${INSTALL_DIR}")
    BACKUP_DIR="${INSTALL_PARENT}/Backup"
fi
mkdir -p "${BACKUP_DIR}"

# 生成备份目录名：nfshm-xframe-bin-backup-<版本号>-<时间戳>
backup_timestamp=$(date '+%Y%m%d_%H%M%S')
backup_dir="${BACKUP_DIR}/nfshm-xframe-bin-backup-${old_version}-${backup_timestamp}"

log_info "备份原安装目录到: ${backup_dir}"
if [ "$SUDO" = "sudo" ]; then
    sudo cp -r "${INSTALL_DIR}" "${backup_dir}"
else
    cp -r "${INSTALL_DIR}" "${backup_dir}"
fi
log_success "备份完成: ${backup_dir}"

# 安全复制文件（支持热更新，即使文件正在运行也能替换）
# 使用原子替换方式：先复制到临时文件，然后重命名
safe_copy_file() {
    local src_file="$1"
    local dest_file="$2"
    local use_sudo="$3"
    
    if [ ! -f "$src_file" ]; then
        return 1
    fi
    
    # 创建目标目录
    local dest_dir=$(dirname "$dest_file")
    if [ "$use_sudo" = "sudo" ]; then
        sudo mkdir -p "$dest_dir"
    else
        mkdir -p "$dest_dir"
    fi
    
    # 使用原子替换方式：先复制到临时文件，然后重命名
    # 这样可以避免"文本文件忙"错误，支持热更新
    local temp_file="${dest_file}.tmp.$$"
    
    # 复制到临时文件
    if [ "$use_sudo" = "sudo" ]; then
        sudo cp --preserve=timestamps "$src_file" "$temp_file" || return 1
        # 原子替换：先删除旧文件（Linux 允许删除正在运行的文件），再移动新文件
        if ! sudo mv "$temp_file" "$dest_file" 2>/dev/null; then
            sudo rm -f "$dest_file" 2>/dev/null || true
            if ! sudo mv "$temp_file" "$dest_file"; then
                sudo rm -f "$temp_file" 2>/dev/null || true
                return 1
            fi
        fi
    else
        cp --preserve=timestamps "$src_file" "$temp_file" || return 1
        # 原子替换：先删除旧文件（Linux 允许删除正在运行的文件），再移动新文件
        if ! mv "$temp_file" "$dest_file" 2>/dev/null; then
            rm -f "$dest_file" 2>/dev/null || true
            if ! mv "$temp_file" "$dest_file"; then
                rm -f "$temp_file" 2>/dev/null || true
                return 1
            fi
        fi
    fi
    
    return 0
}

# 判断文件是否需要安全复制（可执行文件和 .so 文件需要安全复制）
needs_safe_copy() {
    local file="$1"
    local filename=$(basename "$file")
    
    # 检查是否是 .so 文件
    if [[ "$filename" == *.so ]] || [[ "$filename" == *.so.* ]]; then
        return 0
    fi
    
    # 检查是否是可执行文件
    if [ -x "$file" ]; then
        return 0
    fi
    
    return 1
}

# 安全复制目录（递归复制，对 .so 和可执行文件使用安全复制）
safe_copy_directory() {
    local src_dir="$1"
    local dest_dir="$2"
    local use_sudo="$3"
    
    if [ ! -d "$src_dir" ]; then
        return 1
    fi
    
    # 创建目标目录
    if [ "$use_sudo" = "sudo" ]; then
        sudo mkdir -p "$dest_dir"
    else
        mkdir -p "$dest_dir"
    fi
    
    # 遍历源目录
    for item in "${src_dir}"/*; do
        [ -e "$item" ] || continue
        
        local item_name=$(basename "$item")
        local dest_item="${dest_dir}/${item_name}"
        
        if [ -d "$item" ]; then
            # 递归复制子目录
            if ! safe_copy_directory "$item" "$dest_item" "$use_sudo"; then
                return 1
            fi
        elif [ -f "$item" ]; then
            # 判断是否需要安全复制
            if needs_safe_copy "$item"; then
                # 使用安全复制方式（支持热更新）
                if ! safe_copy_file "$item" "$dest_item" "$use_sudo"; then
                    log_error "复制文件失败: $item -> $dest_item"
                    return 1
                fi
            else
                # 普通文件直接复制
                if [ "$use_sudo" = "sudo" ]; then
                    if ! sudo cp --preserve=timestamps "$item" "$dest_item"; then
                        return 1
                    fi
                else
                    if ! cp --preserve=timestamps "$item" "$dest_item"; then
                        return 1
                    fi
                fi
            fi
        fi
    done
    
    return 0
}

# 更新文件（支持热更新）
log_info "开始更新文件到安装目录..."
echo ""

# 复制 Config 目录
if [ -d "Config" ]; then
    log_info "更新 Config/ 目录..."
    find Config -type f | while read -r file; do
        log_info "  复制: $file"
        dest_file="${INSTALL_DIR}/$file"
        $SUDO mkdir -p "$(dirname "$dest_file")"
        $SUDO cp --preserve=timestamps "$file" "$dest_file" 2>/dev/null || true
    done
fi

# 复制 LuaScript 目录
if [ -d "LuaScript" ]; then
    log_info "更新 LuaScript/ 目录..."
    find LuaScript -type f | while read -r file; do
        log_info "  复制: $file"
        dest_file="${INSTALL_DIR}/$file"
        $SUDO mkdir -p "$(dirname "$dest_file")"
        $SUDO cp --preserve=timestamps "$file" "$dest_file" 2>/dev/null || true
    done
fi

# 复制 Plugin 目录（对 .so 文件使用安全复制，支持热更新）
if [ -d "Plugin" ]; then
    log_info "更新 Plugin/ 目录..."
    find Plugin -type f | while read -r file; do
        log_info "  复制: $file"
        dest_file="${INSTALL_DIR}/$file"
        $SUDO mkdir -p "$(dirname "$dest_file")"
        if needs_safe_copy "$file"; then
            safe_copy_file "$file" "$dest_file" "$SUDO" || log_warning "复制失败: $file"
        else
            $SUDO cp --preserve=timestamps "$file" "$dest_file" 2>/dev/null || true
        fi
    done
fi

# 复制 Bin 目录（对 .so 和可执行文件使用安全复制，支持热更新）
if [ -d "Bin" ]; then
    log_info "更新 Bin/ 目录（二进制文件和 .so 文件支持热更新）..."
    find Bin -type f | while read -r file; do
        log_info "  复制: $file"
        dest_file="${INSTALL_DIR}/$file"
        $SUDO mkdir -p "$(dirname "$dest_file")"
        if needs_safe_copy "$file"; then
            safe_copy_file "$file" "$dest_file" "$SUDO" || log_warning "复制失败: $file"
        else
            $SUDO cp --preserve=timestamps "$file" "$dest_file" 2>/dev/null || true
        fi
    done
fi

# 复制可执行文件（NFServerController 等）
log_info "更新可执行文件..."
if [ -f "NFPluginLoader" ]; then
    log_info "  复制: NFPluginLoader"
    if ! safe_copy_file "NFPluginLoader" "${INSTALL_DIR}/NFPluginLoader" "$SUDO"; then
        log_warning "更新 NFPluginLoader 失败"
    fi
fi
if [ -f "NFServerController" ]; then
    log_info "  复制: NFServerController"
    if ! safe_copy_file "NFServerController" "${INSTALL_DIR}/NFServerController" "$SUDO"; then
        log_warning "更新 NFServerController 失败"
    fi
fi

# 复制配置文件
log_info "更新配置文件..."
for file in *.md; do
    [ -f "$file" ] && log_info "  复制: $file" && \
    $SUDO cp --preserve=timestamps "$file" "${INSTALL_DIR}/" 2>/dev/null || true
done
for file in *.txt; do
    [ -f "$file" ] && log_info "  复制: $file" && \
    $SUDO cp --preserve=timestamps "$file" "${INSTALL_DIR}/" 2>/dev/null || true
done
# 复制 .sh 文件，但排除 install.sh 和 update.sh
for sh_file in *.sh; do
    [ -f "$sh_file" ] && [ "$sh_file" != "install.sh" ] && [ "$sh_file" != "update.sh" ] && \
    log_info "  复制: $sh_file" && \
    $SUDO cp --preserve=timestamps "$sh_file" "${INSTALL_DIR}/" 2>/dev/null || true
done
for file in *.conf; do
    [ -f "$file" ] && log_info "  复制: $file" && \
    $SUDO cp --preserve=timestamps "$file" "${INSTALL_DIR}/" 2>/dev/null || true
done

# 设置可执行权限
log_info "设置可执行权限..."
$SUDO chmod +x "${INSTALL_DIR}/NFPluginLoader" 2>/dev/null || true
$SUDO chmod +x "${INSTALL_DIR}/NFServerController" 2>/dev/null || true
$SUDO chmod +x "${INSTALL_DIR}"/*.sh 2>/dev/null || true

# 清理所有残留的临时文件（.tmp.*）
log_info "清理临时文件..."
if [ "$SUDO" = "sudo" ]; then
    sudo find "${INSTALL_DIR}" -type f -name "*.tmp.*" -delete 2>/dev/null || true
else
    find "${INSTALL_DIR}" -type f -name "*.tmp.*" -delete 2>/dev/null || true
fi

echo ""
log_success "文件更新完成"
echo ""

echo "=========================================="
log_success "更新完成！"
echo "=========================================="
echo ""
log_info "安装目录: ${INSTALL_DIR}"
log_info "备份目录: ${backup_dir}"
echo ""
log_warning "重要提示："
log_warning "  1. 如果更新了正在运行的程序，旧进程仍在使用旧版本"
log_warning "  2. 请在方便时重启服务器以应用更新"
log_warning "  3. 如果更新出现问题，可以从备份目录恢复: ${backup_dir}"
echo ""
echo "=========================================="
UPDATE_EOF

    chmod +x "${release_dir}/update.sh"
    
    log_success "更新脚本已生成"
}

# 生成热更服务器脚本
# 参数: $1 - 发布目录（package_content_dir）
#       $2 - 变更文件列表文件路径
generate_hot_reload_script() {
    local release_dir="$1"
    local changed_files="$2"
    
    log_info "生成热更服务器脚本..."
    
    # 检查变更文件列表是否存在
    if [ ! -f "${changed_files}" ] || [ ! -s "${changed_files}" ]; then
        log_warning "变更文件列表为空，跳过热更脚本生成"
        return
    fi
    
    # 查找 Plugin.lua 和 Common.lua 文件
    local plugin_lua=""
    local common_lua=""
    
    # 尝试多个可能的路径
    local possible_plugin_paths=(
        "Install/Plugin/${GAME_DIR}/Plugin.lua"
        "Plugin/${GAME_DIR}/Plugin.lua"
        "Plugin/game/Plugin.lua"
    )
    
    local possible_common_paths=(
        "Install/Plugin/${GAME_DIR}/Common.lua"
        "Plugin/${GAME_DIR}/Common.lua"
        "Plugin/game/Common.lua"
    )
    
    for path in "${possible_plugin_paths[@]}"; do
        if [ -f "${PROJECT_ROOT}/${path}" ]; then
            plugin_lua="${PROJECT_ROOT}/${path}"
            break
        fi
    done
    
    for path in "${possible_common_paths[@]}"; do
        if [ -f "${PROJECT_ROOT}/${path}" ]; then
            common_lua="${PROJECT_ROOT}/${path}"
            break
        fi
    done
    
    if [ -z "$plugin_lua" ] || [ -z "$common_lua" ]; then
        log_warning "未找到 Plugin.lua 或 Common.lua 文件，跳过热更脚本生成"
        log_warning "尝试的路径:"
        log_warning "  Plugin.lua: ${possible_plugin_paths[*]}"
        log_warning "  Common.lua: ${possible_common_paths[*]}"
        return
    fi
    
    log_info "找到配置文件:"
    log_info "  Plugin.lua: ${plugin_lua}"
    log_info "  Common.lua: ${common_lua}"
    
    # 解析 Common.lua 获取服务器类型映射（NF_ST_XXX = 数字）
    declare -A server_type_map
    while IFS= read -r line; do
        # 匹配格式: NF_ST_XXX = 数字; 或 NF_ST_XXX=数字;
        if [[ "$line" =~ ^[[:space:]]*NF_ST_([A-Z_]+)[[:space:]]*=[[:space:]]*([0-9]+) ]]; then
            local server_type_name="NF_ST_${BASH_REMATCH[1]}"
            local server_type_value="${BASH_REMATCH[2]}"
            server_type_map["$server_type_name"]="$server_type_value"
        fi
    done < "$common_lua"
    
    log_info "解析到 ${#server_type_map[@]} 个服务器类型定义"
    
    # 解析 Plugin.lua 获取插件到服务器类型的映射
    declare -A plugin_to_server_type
    local current_server=""
    local current_server_type=""
    local in_plugin_list=false
    local temp_plugins=()
    
    while IFS= read -r line; do
        # 匹配服务器定义开始: ServerName = {
        if [[ "$line" =~ ^[[:space:]]*([A-Za-z0-9_]+)[[:space:]]*=[[:space:]]*\{ ]]; then
            current_server="${BASH_REMATCH[1]}"
            current_server_type=""
            temp_plugins=()
            in_plugin_list=false
        fi
        
        # 匹配插件列表开始: WorkPlugins = { 或 ServerPlugins = { 或 FramePlugins = {
        if [[ "$line" =~ (WorkPlugins|ServerPlugins|FramePlugins)[[:space:]]*=[[:space:]]*\{ ]]; then
            in_plugin_list=true
        fi
        
        # 匹配插件名（在 WorkPlugins、ServerPlugins 或 FramePlugins 中）
        if [ "$in_plugin_list" = true ] && [[ "$line" =~ \"([A-Za-z0-9_]+Plugin)\" ]]; then
            local plugin_name="${BASH_REMATCH[1]}"
            temp_plugins+=("$plugin_name")
        fi
        
        # 匹配插件列表结束: };
        # 匹配格式: 制表符或空格 + };
        if [[ "$line" =~ ^[[:space:]]*\}.*\; ]]; then
            in_plugin_list=false
        fi
        
        # 匹配 ServerType = NF_ST_XXX;
        if [[ "$line" =~ ServerType[[:space:]]*=[[:space:]]*(NF_ST_[A-Z_]+) ]]; then
            current_server_type="${BASH_REMATCH[1]}"
            # 将当前收集的插件映射到服务器类型
            if [ -n "$current_server_type" ] && [ -n "${server_type_map[$current_server_type]}" ]; then
                local server_type_value="${server_type_map[$current_server_type]}"
                # 检查 temp_plugins 数组是否有元素
                if [ ${#temp_plugins[@]} -gt 0 ]; then
                    for plugin_name in "${temp_plugins[@]}"; do
                        plugin_to_server_type["$plugin_name"]="$server_type_value"
                    done
                fi
            fi
        fi
        
        # 匹配服务器定义结束: },
        if [[ "$line" =~ ^[[:space:]]*\}, ]]; then
            current_server=""
            current_server_type=""
            temp_plugins=()
            in_plugin_list=false
        fi
    done < "$plugin_lua"
    
    log_info "解析到 ${#plugin_to_server_type[@]} 个插件到服务器类型的映射"
    
    # 分析变更文件
    local has_bin_change=false
    local has_config_change=false
    local server_types_to_restart=()
    
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        
        # 检查是否是 bin 文件（可执行文件，非 .so）
        if [[ "$file" == Bin/* ]] && [[ ! "$file" == *.so ]] && [[ ! "$file" == *.so.* ]]; then
            has_bin_change=true
        fi
        
        # 检查是否是 Config/ 目录下的文件
        if [[ "$file" == Config/* ]] || [[ "$file" == */Config/* ]]; then
            has_config_change=true
        fi
        
        # 检查是否是 .so 文件
        if [[ "$file" == *.so ]] || [[ "$file" == *.so.* ]]; then
            # 从文件路径提取插件名
            # 例如: Bin/libNFMMOLogicPlayerPlugin.so -> NFMMOLogicPlayerPlugin
            local basename=$(basename "$file")
            local plugin_name=""
            
            # 移除 lib 前缀和 .so 后缀
            if [[ "$basename" =~ ^lib(.+)\.so ]]; then
                plugin_name="${BASH_REMATCH[1]}"
            elif [[ "$basename" =~ ^(.+)\.so ]]; then
                plugin_name="${BASH_REMATCH[1]}"
            fi
            
            if [ -n "$plugin_name" ] && [ -n "${plugin_to_server_type[$plugin_name]}" ]; then
                local server_type="${plugin_to_server_type[$plugin_name]}"
                # 检查是否已经添加过这个服务器类型
                local found=false
                if [ ${#server_types_to_restart[@]} -gt 0 ]; then
                    for st in "${server_types_to_restart[@]}"; do
                        if [ "$st" = "$server_type" ]; then
                            found=true
                            break
                        fi
                    done
                fi
                if [ "$found" = false ]; then
                    server_types_to_restart+=("$server_type")
                    log_info "检测到插件 ${plugin_name} 变更，需要重启服务器类型: ${server_type}"
                fi
            else
                log_warning "无法确定插件 ${plugin_name} 对应的服务器类型，将重启所有服务器"
                has_bin_change=true
            fi
        fi
    done < "$changed_files"
    
    # 生成热更脚本
    local script_name="run_all_${VERSION}.sh"
    local script_path="${release_dir}/${script_name}"
    
    cat > "$script_path" <<'SCRIPT_EOF'
#!/bin/bash
# ============================================================================
# NFShmXFrame 热更服务器脚本
# ============================================================================
# 本脚本根据变更的文件自动生成热更命令
# 使用方法: ./run_all_*.sh

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

echo "=========================================="
echo "NFShmXFrame 热更服务器脚本"
echo "=========================================="

# 检查 NFServerController 是否存在
if [ ! -f "./NFServerController" ] && [ ! -f "./NFServerController.exe" ]; then
    log_error "NFServerController 不存在，请确保在正确的目录下运行此脚本"
    exit 1
fi

CONTROLLER="./NFServerController"
if [ -f "./NFServerController.exe" ]; then
    CONTROLLER="./NFServerController.exe"
fi

SCRIPT_EOF

    # 添加热更命令
    if [ "$has_bin_change" = true ]; then
        cat >> "$script_path" <<'SCRIPT_EOF'

# Bin 文件变更，重启所有服务器
log_info "检测到 Bin 文件变更，重启所有服务器..."
${CONTROLLER} "restart *.*.*.*"
if [ $? -eq 0 ]; then
    log_success "所有服务器重启成功"
else
    log_error "服务器重启失败"
    exit 1
fi
SCRIPT_EOF
    elif [ ${#server_types_to_restart[@]} -gt 0 ]; then
        cat >> "$script_path" <<'SCRIPT_EOF'

# .so 文件变更，重启对应的服务器类型
SCRIPT_EOF
        for server_type in "${server_types_to_restart[@]}"; do
            {
                echo "log_info \"重启服务器类型: ${server_type} (*.*.${server_type}.*)\""
                echo "\${CONTROLLER} \"restart *.*.${server_type}.*\""
                echo "if [ \$? -eq 0 ]; then"
                echo "    log_success \"服务器类型 ${server_type} 重启成功\""
                echo "else"
                echo "    log_error \"服务器类型 ${server_type} 重启失败\""
                echo "    exit 1"
                echo "fi"
            } >> "$script_path"
        done
    fi
    
    if [ "$has_config_change" = true ]; then
        cat >> "$script_path" <<'SCRIPT_EOF'

# Config 文件变更，重载所有服务器配置
log_info "检测到 Config 文件变更，重载所有服务器配置..."
${CONTROLLER} "reload *.*.*.*"
if [ $? -eq 0 ]; then
    log_success "所有服务器配置重载成功"
else
    log_error "服务器配置重载失败"
    exit 1
fi
SCRIPT_EOF
    fi
    
    # 如果没有需要热更的内容
    if [ "$has_bin_change" = false ] && [ ${#server_types_to_restart[@]} -eq 0 ] && [ "$has_config_change" = false ]; then
        cat >> "$script_path" <<'SCRIPT_EOF'

# 没有需要热更的内容
log_info "没有检测到需要热更的文件（Bin 文件、.so 文件或 Config 文件）"
SCRIPT_EOF
    fi
    
    cat >> "$script_path" <<'SCRIPT_EOF'

echo "=========================================="
log_success "热更脚本执行完成"
echo "=========================================="
SCRIPT_EOF

    chmod +x "$script_path"
    
    log_success "热更脚本已生成: ${script_name}"
    if [ "$has_bin_change" = true ]; then
        log_info "  检测到 Bin 文件变更，将重启所有服务器"
    fi
    if [ ${#server_types_to_restart[@]} -gt 0 ]; then
        log_info "  检测到 .so 文件变更，将重启以下服务器类型:"
        for server_type in "${server_types_to_restart[@]}"; do
            log_info "    - *.*.${server_type}.*"
        done
    fi
    if [ "$has_config_change" = true ]; then
        log_info "  检测到 Config 文件变更，将重载所有服务器配置"
    fi
}

# 主流程
main() {
    # 清理输出目录中的旧内容（保留压缩文件）
    cleanup_output_dir
    
    # 清理旧的安装目录
    cleanup_install_dir
    
    # 使用直接复制方式安装文件
    copy_files_directly
    
    # 根据打包类型执行不同的操作
    if [ "$PACKAGE_TYPE" = "all" ]; then
        # 全量包
        create_all_package
    else
        # 增量包
        # 检查是否有源码文件被修改（如果有，提示用户需要重新编译）
        if [ -n "$FROM_COMMIT" ]; then
            local source_files_changed=$(git diff --name-only --diff-filter=ACMR "${FROM_COMMIT}^..HEAD" 2>/dev/null | \
                                        grep -E '\.(cpp|c|h|hpp|cc|cxx)$' || true)
            if [ -n "$source_files_changed" ]; then
                log_warning "检测到源码文件被修改，请确保已经重新编译（使用 make clean && make）"
                log_warning "修改的源码文件："
                echo "$source_files_changed" | while read -r file; do
                    log_warning "  - $file"
                done
            fi
        fi
        
        # 先创建临时目录生成当前版本的 manifest（用于对比）
        local temp_manifest_dir=$(mktemp -d /tmp/manifest-$$.XXXXXX)
        generate_manifest "${temp_manifest_dir}"
        local current_manifest="${temp_manifest_dir}/manifest.md"
        
        # 对比清单并获取变更文件列表
        local changed_files=$(compare_manifests "${current_manifest}" "${BASE_VERSION}")
        
        # 检查是否有变更
        local has_changes=false
        if [ -n "$changed_files" ] && [ -f "$changed_files" ] && [ -s "$changed_files" ]; then
            has_changes=true
        fi
        
        # 只有在成功创建增量包后才清理临时目录
        if [ "$has_changes" = true ]; then
            # 创建增量包（manifest 会在 create_patch_package 中生成）
            create_patch_package "${changed_files}"
            # 清理临时目录
            rm -rf "${temp_manifest_dir}"
        else
            # 没有变更时保留临时目录用于调试
            log_warning "临时 manifest 已保留用于调试: ${temp_manifest_dir}"
            log_warning "你可以手动检查: ${current_manifest}"
            log_warning "使用完成后请手动删除: rm -rf ${temp_manifest_dir}"
            # 仍然调用 create_patch_package（会显示警告信息）
            create_patch_package "${changed_files}"
        fi
    fi
    
    log_success "=========================================="
    log_success "打包完成！"
    log_success "=========================================="
}

# 执行主流程
main "$@"


